/*
* RapidMiner
*
* Copyright (C) 2001-2011 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.operator.visualization.dependencies;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import Jama.Matrix;
import com.rapidminer.example.Attribute;
import com.rapidminer.example.Example;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.UserError;
import com.rapidminer.operator.ports.InputPort;
import com.rapidminer.operator.ports.OutputPort;
import com.rapidminer.operator.ports.metadata.AttributeSetPrecondition;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeAttribute;
import com.rapidminer.parameter.ParameterTypeBoolean;
import com.rapidminer.tools.Ontology;
/**
* <p>This operator calculates the rainflow matrix for a series attribute. Please note that
* the attribute does have to be discretized before. Since this operator relies on the
* fact that the names of the bins follow the same natural order than the values they
* represent, we recommend to use the option "interval names" for the
* discretized bins.</p>
*
* @author Ingo Mierswa
*/
public class RainflowMatrixOperator extends Operator {
public static final String PARAMETER_ATTRIBUTE = "attribute";
public static final String PARAMETER_SYMMETRICAL_MATRIX = "symmetrical_matrix";
private InputPort exampleSetInput = getInputPorts().createPort("example set", ExampleSet.class);
private OutputPort exampleSetOutput = getOutputPorts().createPort("example set");
private OutputPort matrixOutput = getOutputPorts().createPort("transition matrix");
public RainflowMatrixOperator(OperatorDescription description) {
super(description);
exampleSetInput.addPrecondition(new AttributeSetPrecondition(exampleSetInput, AttributeSetPrecondition.getAttributesByParameter(this, PARAMETER_ATTRIBUTE), Ontology.NOMINAL));
getTransformer().addPassThroughRule(exampleSetInput, exampleSetOutput);
getTransformer().addGenerationRule(matrixOutput, NumericalMatrix.class);
}
@Override
public void doWork() throws OperatorException {
ExampleSet exampleSet = exampleSetInput.getData();
String attributeName = getParameterAsString(PARAMETER_ATTRIBUTE);
Attribute attribute = exampleSet.getAttributes().get(attributeName);
if (attribute == null) {
throw new UserError(this, 111, attributeName);
}
if (!attribute.isNominal()) {
throw new UserError(this, 103, "calculation of the Rainflow Matrix", attributeName);
}
// store all nominal values in our working list
List<String> values = new ArrayList<String>(exampleSet.size());
for (Example example : exampleSet) {
if (!Double.isNaN(example.getValue(attribute))) {
values.add(example.getNominalValue(attribute));
}
}
// remove all non extrema
removeNonExtrema(values);
// initialize matrix and "column" names
List<String> allNames = new LinkedList<String>();
for (String name : attribute.getMapping().getValues()) {
allNames.add(name);
}
String[] columnNames = new String[allNames.size()];
allNames.toArray(columnNames);
Map<String, Integer> indexMap = new HashMap<String, Integer>();
for (int i = 0; i < columnNames.length; i++) {
indexMap.put(columnNames[i], i);
}
// fill matrix (4 point algorithm)
double[][] counts = createRainflowCounts(values, indexMap);
// wrap entries if symmetrical
boolean symmetrical = getParameterAsBoolean(PARAMETER_SYMMETRICAL_MATRIX);
if (symmetrical) {
for (int x = 1; x < counts.length - 1; x++) {
for (int y = 1; y < counts[x].length - 1; y++) {
counts[y][x] += counts[x][y];
counts[x][y] = 0;
}
}
}
// create and deliver results
Matrix matrix = new Matrix(counts);
String[] residuals = new String[values.size()];
values.toArray(residuals);
RainflowMatrix rainflowMatrix = new RainflowMatrix("Rainflow Matrix", columnNames, matrix, symmetrical, residuals);
rainflowMatrix.setFirstAttributeName("From Value");
rainflowMatrix.setSecondAttributeName("To Value");
exampleSetOutput.deliver(exampleSet);
matrixOutput.deliver(rainflowMatrix);
}
private double[][] createRainflowCounts(List<String> values, Map<String, Integer> indexMap) {
double[][] counts = new double[indexMap.size()][indexMap.size()];
int currentIndex = 4;
while (currentIndex < values.size()) {
String value1 = values.get(currentIndex - 4);
String value2 = values.get(currentIndex - 3);
String value3 = values.get(currentIndex - 2);
String value4 = values.get(currentIndex - 1);
boolean betweenIncreasing = (value2.compareTo(value1) >= 0) && (value2.compareTo(value4) <= 0) && (value3.compareTo(value1) >= 0) && (value3.compareTo(value4) <= 0);
boolean betweenDecreasing = (value2.compareTo(value1) <= 0) && (value2.compareTo(value4) >= 0) && (value3.compareTo(value1) <= 0) && (value3.compareTo(value4) >= 0);
if (betweenIncreasing || betweenDecreasing) {
int fromIndex = indexMap.get(value2);
int toIndex = indexMap.get(value3);
counts[fromIndex][toIndex] = counts[fromIndex][toIndex] + 1;
values.remove(currentIndex - 2);
values.remove(currentIndex - 3);
currentIndex = 4;
} else { // nothing found
currentIndex++;
}
}
return counts;
}
private void removeNonExtrema(List<String> values) {
for (int i = values.size() - 2; i >= 1; i--) {
String currentValue = values.get(i);
String beforeValue = values.get(i-1);
String afterValue = values.get(i+1);
boolean localMinimum = beforeValue.compareTo(currentValue) > 0 && afterValue.compareTo(currentValue) > 0;
boolean localMaximum = beforeValue.compareTo(currentValue) < 0 && afterValue.compareTo(currentValue) < 0;
if (!localMinimum && !localMaximum) {
values.remove(i);
}
}
}
@Override
public List<ParameterType> getParameterTypes() {
List<ParameterType> types = super.getParameterTypes();
types.add(new ParameterTypeAttribute(PARAMETER_ATTRIBUTE, "Indicates which attribute should be used as a base for the calculation of the Rainflow matrix.", exampleSetInput, false, Ontology.NOMINAL));
types.add(new ParameterTypeBoolean(PARAMETER_SYMMETRICAL_MATRIX, "Indicates if the symmetrical matrix should be calculated.", false));
return types;
}
}