/* * 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.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import com.rapidminer.operator.Operator; import com.rapidminer.operator.OperatorDescription; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.SimpleProcessSetupError; import com.rapidminer.operator.UserError; import com.rapidminer.operator.ProcessSetupError.Severity; import com.rapidminer.operator.ports.DummyPortPairExtender; import com.rapidminer.operator.ports.PortPairExtender; import com.rapidminer.operator.ports.quickfix.ParameterSettingQuickFix; import com.rapidminer.parameter.ParameterType; import com.rapidminer.parameter.ParameterTypeList; import com.rapidminer.parameter.ParameterTypeString; import com.rapidminer.parameter.UndefinedParameterError; /** * Sets a list of parameters using existing parameter values. <br/> * * The operator is similar to {@link ParameterSetter}, but differs from that in not requiring a ParameterSet input. It * simply reads a parameter value from a source and uses it to set the parameter value of a target parameter. Both, * source and target, are given in the format 'operator'.'parameter'. <br/> * * This operator is more general than ParameterSetter and could completely replace it. It is most useful, if you need a * parameter which is optimized more than once within the optimization loop - ParameterSetter cannot be used here. <br/> * * These parameters can either be generated by a {@link ParameterOptimizationOperator} or read by a * {@link com.rapidminer.operator.io.ParameterSetLoader}. This operator is useful, e.g. in the following scenario. If * one wants to find the best parameters for a certain learning scheme, one usually is also interested in the model * generated with this parameters. While the first is easily possible using a {@link ParameterOptimizationOperator}, the * latter is not possible because the {@link ParameterOptimizationOperator} does not return the IOObjects produced * within, but only a parameter set. This is, because the parameter optimization operator knows nothing about models, * but only about the performance vectors produced within. Producing performance vectors does not necessarily require a * model. <br/> * To solve this problem, one can use a <code>ParameterSetter</code>. Usually, a process definition with a * <code>ParameterSetter</code> contains at least two operators of the same type, typically a learner. One learner may * be an inner operator of the {@link ParameterOptimizationOperator} and may be named "Learner", whereas a * second learner of the same type named "OptimalLearner" follows the parameter optimization and should use * the optimal parameter set found by the optimization. In order to make the <code>ParameterSetter</code> set the * optimal parameters of the right operator, one must specify its name. Therefore, the parameter list * <var>name_map</var> was introduced. Each parameter in this list maps the name of an operator that was used during * optimization (in our case this is "Learner") to an operator that should now use these parameters (in our * case this is "OptimalLearner"). * * @author Robert Rudolph */ public class ParameterCloner extends Operator { /** * The parameter name for "A list mapping operator parameters from the set to other operator parameters in the * process setup." */ public static final String PARAMETER_NAME_MAP = "name_map"; private PortPairExtender dummyPorts = new DummyPortPairExtender("through", getInputPorts(), getOutputPorts()); public ParameterCloner(OperatorDescription description) { super(description); dummyPorts.start(); getTransformer().addRule(dummyPorts.makePassThroughRule()); } @Override public void doWork() throws OperatorException { List<String[]> nameList = getParameterList(PARAMETER_NAME_MAP); Iterator<String[]> i = nameList.iterator(); while (i.hasNext()) { String[] keyValue = i.next(); String[] source = keyValue[0].split("\\."); String[] target = keyValue[1].split("\\."); if (source.length != 2) throw new UserError(this, 907, keyValue[0]); if (target.length != 2) throw new UserError(this, 907, keyValue[1]); Operator operator = lookupOperator(source[0]); if (operator == null) throw new UserError(this, 109, source[0]); String value = operator.getParameter(source[1]); if (value == null) throw new UserError(this, 213, new Object[] { source[1], source[0], this.getName() }); operator = lookupOperator(target[0]); if (operator == null) throw new UserError(this, 109, target[0]); operator.getParameters().setParameter(target[1], value); } dummyPorts.passDataThrough(); } @Override public int checkProperties() { int errorNumber = 0; try { final List<String[]> nameList = getParameterList(PARAMETER_NAME_MAP); ; if (nameList.size() > 0) { // check if valid operator names are present HashSet<String> operatorNames = new HashSet<String>(); operatorNames.addAll(getProcess().getAllOperatorNames()); for (final String[] pair : nameList) { for (String operatorParameter : pair) { String[] parts = operatorParameter.split("\\."); if (parts.length != 2) { errorNumber++; addError(new SimpleProcessSetupError(Severity.ERROR, this.getPortOwner(), Collections.singletonList(new ParameterSettingQuickFix(this, PARAMETER_NAME_MAP, getParameterAsString(PARAMETER_NAME_MAP))), "parameter_wrong_format", PARAMETER_NAME_MAP)); } if (!operatorNames.contains(parts[0])) { errorNumber++; addError(new SimpleProcessSetupError(Severity.ERROR, this.getPortOwner(), Collections.singletonList(new ParameterSettingQuickFix(this, PARAMETER_NAME_MAP, getParameterAsString(PARAMETER_NAME_MAP))), "parameter_unknown_operator", PARAMETER_NAME_MAP, parts[0])); } else { Operator operator = lookupOperator(parts[0]); ParameterType type = operator.getParameterType(parts[1]); if (type == null) { errorNumber++; addError(new SimpleProcessSetupError(Severity.ERROR, getPortOwner(), Collections.singletonList(new ParameterSettingQuickFix(this, PARAMETER_NAME_MAP, getParameterAsString(PARAMETER_NAME_MAP))), "parameter_unknown_parameter_for_operator", parts[1], parts[0])); } } } } } else { // list empty: add quickfix for opening definition window errorNumber++; addError(new SimpleProcessSetupError(Severity.WARNING, this.getPortOwner(), Collections.singletonList(new ParameterSettingQuickFix(this, PARAMETER_NAME_MAP, "")), "parameter_list_undefined", "the mapping of operator names")); } } catch (UndefinedParameterError e) { // list is undefined: Add quickfix for opening definition window errorNumber++; addError(new SimpleProcessSetupError(Severity.WARNING, this.getPortOwner(), Collections.singletonList(new ParameterSettingQuickFix(this, PARAMETER_NAME_MAP, "")), "parameter_list_undefined", "the mapping of operator names")); } return errorNumber + super.checkProperties(); } @Override public List<ParameterType> getParameterTypes() { List<ParameterType> types = super.getParameterTypes(); types.add(new ParameterTypeList(PARAMETER_NAME_MAP, "A list mapping operator parameters from the set to other operator parameters in the process setup.", new ParameterTypeString("source", "The source parameter, specified as 'operator'.'parameter'. This value is copied to the target parameter."), new ParameterTypeString("target", "The target parameter, specified as 'operator'.'parameter'."), false)); return types; } }