/*
* 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.meta;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import com.rapidminer.RapidMiner;
import com.rapidminer.gui.properties.ConfigureParameterOptimizationDialogCreator;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorChain;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.ProcessSetupError.Severity;
import com.rapidminer.operator.SimpleProcessSetupError;
import com.rapidminer.operator.UserError;
import com.rapidminer.operator.performance.PerformanceVector;
import com.rapidminer.operator.ports.InputPort;
import com.rapidminer.operator.ports.PortPairExtender;
import com.rapidminer.operator.ports.metadata.MetaData;
import com.rapidminer.operator.ports.metadata.SimplePrecondition;
import com.rapidminer.operator.ports.metadata.SubprocessTransformRule;
import com.rapidminer.operator.ports.quickfix.ParameterSettingQuickFix;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeConfiguration;
import com.rapidminer.parameter.ParameterTypeInnerOperator;
import com.rapidminer.parameter.ParameterTypeList;
import com.rapidminer.parameter.ParameterTypeParameterValue;
import com.rapidminer.parameter.ParameterTypeString;
import com.rapidminer.parameter.ParameterTypeTupel;
import com.rapidminer.parameter.UndefinedParameterError;
import com.rapidminer.parameter.value.ParameterValueGrid;
import com.rapidminer.parameter.value.ParameterValueList;
import com.rapidminer.parameter.value.ParameterValueRange;
import com.rapidminer.parameter.value.ParameterValues;
import com.rapidminer.tools.ParameterService;
/**
* Provides an operator chain which operates on given parameters
* depending on specified values for these parameters.
*
* @author Tobias Malbrecht
*/
public abstract class ParameterIteratingOperatorChain extends OperatorChain {
/** The parameter name for "Parameters to optimize in the format OPERATORNAME.PARAMETERNAME." */
public static final String PARAMETER_PARAMETERS = "parameters";
/** A specification of the parameter values for a parameter." */
public static final String PARAMETER_VALUES = "values";
/** Means that the parameter iteration scheme can only handle discrete parameter values (i.e. lists or numerical grids). */
public static final int VALUE_MODE_DISCRETE = 0;
/** Means that the parameter iteration scheme can only handle intervals of numerical values. */
public static final int VALUE_MODE_CONTINUOUS = 1;
private static final int PARAMETER_VALUES_ARRAY_LENGTH_RANGE = 2;
private static final int PARAMETER_VALUES_ARRAY_LENGTH_GRID = 3;
private static final int PARAMETER_VALUES_ARRAY_LENGTH_SCALED_GRID = 4;
private static final String PARAMETER_OPERATOR_PARAMETER_PAIR = "operator_parameter_pair";
private static final String PARAMETER_OPERATOR = "operator_name";
private static final String PARAMETER_PARAMETER = "parameter_name";
private final PortPairExtender inputExtender = new PortPairExtender("input", getInputPorts(), getSubprocess(0).getInnerSources());
private final InputPort performanceInnerSink = getSubprocess(0).getInnerSinks().createPort("performance");
private final PortPairExtender innerSinkExtender;
public ParameterIteratingOperatorChain(OperatorDescription description) {
this(description, "Subprocess");
}
public ParameterIteratingOperatorChain(OperatorDescription description, String subprocessName) {
super(description, subprocessName);
innerSinkExtender = makeInnerSinkExtender();
inputExtender.start();
innerSinkExtender.start();
getPerformanceInnerSink().addPrecondition(new SimplePrecondition(getPerformanceInnerSink(), new MetaData(PerformanceVector.class), isPerformanceRequired()));
getTransformer().addRule(inputExtender.makePassThroughRule());
getTransformer().addRule(new SubprocessTransformRule(getSubprocess(0)));
getTransformer().addRule(innerSinkExtender.makePassThroughRule());
}
protected abstract PortPairExtender makeInnerSinkExtender();
protected PortPairExtender getInnerSinkExtender() {
return innerSinkExtender;
}
protected InputPort getPerformanceInnerSink() {
return performanceInnerSink;
}
/** Signals whether the subprocess must create a performance vector. */
protected abstract boolean isPerformanceRequired();
/**
* Has to return one of the predefined modes which indicate whether the
* operator takes discrete values or intervals as basis for optimization.
* The first option is to be taken for all strategies that iterate over the
* given parameters. The latter option is to be taken for strategies such
* as an evolutionary one in which allowed ranges of parameters have to be
* specified.
*/
public abstract int getParameterValueMode();
/**
* Parses a parameter list and creates the corresponding data structures.
*/
public List<ParameterValues> parseParameterValues(List<String[]> parameterList) throws OperatorException {
if (getProcess() == null) {
getLogger().warning("Cannot parse parameters while operator is not attached to a process.");
return Collections.<ParameterValues>emptyList();
}
List<ParameterValues> parameterValuesList = new LinkedList<ParameterValues>();
for (String[] pair: parameterList) {
String[] operatorParameter = ParameterTypeTupel.transformString2Tupel(pair[0]);
if (operatorParameter.length != 2)
throw new UserError(this, 907, pair[0]);
Operator operator = lookupOperator(operatorParameter[0]);
if (operator == null)
throw new UserError(this, 109, operatorParameter[0]);
ParameterType parameterType = operator.getParameters().getParameterType(operatorParameter[1]);
if (parameterType == null) {
throw new UserError(this, 906, operatorParameter[0] + "." + operatorParameter[1]);
}
String parameterValuesString = pair[1];
ParameterValues parameterValues = null;
try {
int startIndex = parameterValuesString.indexOf("[");
if (startIndex >= 0) {
int endIndex = parameterValuesString.indexOf("]");
if (endIndex > startIndex) {
String[] parameterValuesArray = parameterValuesString.substring(startIndex + 1, endIndex).trim().split("[;:,]");
switch (parameterValuesArray.length) {
case PARAMETER_VALUES_ARRAY_LENGTH_RANGE: { // value range: [minValue;maxValue]
// double min = Double.parseDouble(parameterValuesArray[0]);
// double max = Double.parseDouble(parameterValuesArray[1]);
parameterValues = new ParameterValueRange(operator, parameterType, parameterValuesArray[0], parameterValuesArray[1]);
}
break;
case PARAMETER_VALUES_ARRAY_LENGTH_GRID: { // value grid: [minValue;maxValue;stepSize]
// double min = Double.parseDouble(parameterValuesArray[0]);
// double max = Double.parseDouble(parameterValuesArray[1]);
// double stepSize = Double.parseDouble(parameterValuesArray[2]);
// if (stepSize == 0) {
// throw new Exception("step size of 0 is not allowed");
// }
// if (min <= max + stepSize) {
// throw new Exception("end value must at least be as large as start value plus step size");
// }
parameterValues = new ParameterValueGrid(operator, parameterType, parameterValuesArray[0], parameterValuesArray[1], parameterValuesArray[2]);
}
break;
case PARAMETER_VALUES_ARRAY_LENGTH_SCALED_GRID: { // value grid: [minValue;maxValue;noOfSteps;scale]
// double min = Double.parseDouble(parameterValuesArray[0]);
// double max = Double.parseDouble(parameterValuesArray[1]);
// int steps = Integer.parseInt(parameterValuesArray[2]);
// if (steps == 0) {
// throw new Exception("step size of 0 is not allowed");
// }
// String scaleName = parameterValuesArray[3];
parameterValues = new ParameterValueGrid(operator, parameterType, parameterValuesArray[0], parameterValuesArray[1], parameterValuesArray[2], parameterValuesArray[3]);
}
break;
default:
throw new Exception("parameter values string could not be parsed (too many arguments)");
}
} else {
throw new Exception("']' was missing");
}
} else {
int colonIndex = parameterValuesString.indexOf(":");
if (colonIndex >= 0) {
// maintain compatibility for evolutionary parameter optimization (old format: startValue:endValue without parantheses)
String[] parameterValuesArray = parameterValuesString.trim().split(":");
if (parameterValuesArray.length != 2) {
throw new Exception("wrong parameter range format");
} else {
// double min = Double.parseDouble(parameterValuesArray[0]);
// double max = Double.parseDouble(parameterValuesArray[1]);
parameterValues = new ParameterValueRange(operator, parameterType, parameterValuesArray[0], parameterValuesArray[1]);
}
} else {
// usual parameter value list: value1,value2,value3,...
if (parameterValuesString.length() != 0) {
String[] values = parameterValuesString.split(",");
parameterValues = new ParameterValueList(operator, parameterType, values);
}
}
}
} catch (Throwable e) {
throw new UserError(this, 116, pair[0], "Unknown parameter value specification format: '" + pair[1] + "'. Error: " + e.getMessage());
}
if (parameterValues != null) {
parameterValuesList.add(parameterValues);
}
}
return parameterValuesList;
}
protected void executeSubprocess() throws OperatorException {
getSubprocess(0).execute();
}
/**
* Applies the inner operator and employs the PerformanceEvaluator for
* calculating a list of performance criteria which is returned.
*/
protected PerformanceVector getPerformance() {
return getPerformance(true);
}
protected PerformanceVector getPerformance(boolean cloneInput) {
try {
inputExtender.passDataThrough();
executeSubprocess();
if (isPerformanceRequired()) {
return getPerformanceInnerSink().getData();
} else {
return getPerformanceInnerSink().getDataOrNull();
}
} catch (OperatorException e) {
StringBuilder builder = new StringBuilder();
builder.append(this.getName());
builder.append(": Cannot evaluate performance for current parameter combination because of an error in one of the inner operators: ");
builder.append(e.getMessage());
getLogger().severe(builder.toString());
// getLogger().severe("Cannot evaluate performance for current parameter combination: " + e.getMessage());
if (Boolean.parseBoolean(ParameterService.getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_DEBUGMODE)))
e.printStackTrace();
return null;
}
}
/** Returns the results at the inner sink port extender. Does not include
* a possible performance vector at the respective input. {@link #executeSubprocess()}
* or {@link #getPerformance()} must have been called earlier.
* @throws UserError */
protected Collection<IOObject> getInnerResults() throws UserError {
return innerSinkExtender.getData();
}
/** Passes data from the inner sinks to the output ports. */
protected void passResultsThrough() {
innerSinkExtender.passDataThrough();
}
@Override
public List<ParameterType> getParameterTypes() {
List<ParameterType> types = super.getParameterTypes();
ParameterType type = new ParameterTypeConfiguration(ConfigureParameterOptimizationDialogCreator.class, this);
type.setExpert(false);
types.add(type);
type = new ParameterTypeList(PARAMETER_PARAMETERS, "The parameters.",
new ParameterTypeTupel(PARAMETER_OPERATOR_PARAMETER_PAIR, "The operator and it's parameter",
new ParameterTypeInnerOperator(PARAMETER_OPERATOR, "The operator."),
new ParameterTypeString(PARAMETER_PARAMETER, "The parameter.")),
new ParameterTypeParameterValue(PARAMETER_VALUES, "The value specifications for the parameters."));
type.setHidden(true);
types.add(type);
return types;
}
@Override
public int checkProperties() {
boolean parametersPresent = false;
try {
List<ParameterValues> list = parseParameterValues(getParameterList(PARAMETER_PARAMETERS));
if (list != null && list.size() > 0) {
parametersPresent = true;
}
} catch (UndefinedParameterError e) {
} catch (OperatorException e) { }
if (!parametersPresent) {
addError(new SimpleProcessSetupError(Severity.ERROR, this.getPortOwner(), Collections.singletonList(new ParameterSettingQuickFix(this, ParameterTypeConfiguration.PARAMETER_DEFAULT_CONFIGURATION_NAME)), "parameter_combination_undefined"));
}
return super.checkProperties();
}
}