/* * 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(); } }