/*
* RapidMiner
*
* Copyright (C) 2001-2008 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;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import com.rapidminer.datatable.DataTable;
import com.rapidminer.datatable.DataTableRow;
import com.rapidminer.datatable.SimpleDataTable;
import com.rapidminer.datatable.SimpleDataTableRow;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.UserError;
import com.rapidminer.operator.Value;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeBoolean;
import com.rapidminer.parameter.ParameterTypeFile;
import com.rapidminer.parameter.ParameterTypeList;
import com.rapidminer.parameter.ParameterTypeValue;
import com.rapidminer.parameter.UndefinedParameterError;
/**
* This operator records almost arbitrary data. It can written to a file which
* can be read e.g. by gnuplot. Alternatively, the collected data can be plotted
* by the GUI. This is even possible during process runtime (i.e. online
* plotting).<br/>
*
* Parameters in the list <code>log</code> are interpreted as follows: The
* <var>key</var> gives the name for the column name (e.g. for use in the
* plotter). The <var>value</var> specifies where to retrieve the value from.
* This is best explained by an example:
* <ul>
* <li>If the value is <code>operator.Evaluator.value.absolute</code>, the
* ProcessLogOperator looks up the operator with the name
* <code>Evaluator</code>. If this operator is a
* {@link com.rapidminer.operator.performance.PerformanceEvaluator}, it has a
* value named <var>absolute</var> which gives the absolute error of the last
* evaluation. This value is queried by the ProcessLogOperator</li>
* <li>If the value is <code>operator.SVMLearner.parameter.C</code>, the
* ProcessLogOperator looks up the parameter <var>C</var> of the operator
* named <code>SVMLearner</code>.</li>
* </ul>
* Each time the ProcessLogOperator is applied, all the values and parameters
* specified by the list <var>log</var> are collected and stored in a data row.
* When the process finishes, the operator writes the collected data rows to
* a file (if specified). In GUI mode, 2D or 3D plots are automatically
* generated and displayed in the result viewer. <br/> Please refer to section
* {@rapidminer.ref sec:parameter_optimization|Advanced Processes/Parameter and performance analysis}
* for an example application.
*
* @rapidminer.todo Use IOObjects for logging as well (e.g.
* {@link com.rapidminer.operator.performance.PerformanceVector})
* @author Simon Fischer, Ingo Mierswa
* @version $Id: ProcessLogOperator.java,v 2.27 2006/03/27 13:21:58
* ingomierswa Exp $
*/
public class ProcessLogOperator extends Operator {
/** The parameter name for "operator.OPERATORNAME.[value|parameter].VALUE_NAME" */
public static final String PARAMETER_COLUMN_NAME = "column_name";
public static final String PARAMETER_FILENAME = "filename";
public static final String PARAMETER_LOG = "log";
private static final String PARAMETER_PERSISTENT = "persistent";
public ProcessLogOperator(OperatorDescription description) {
super(description);
}
private double fetchValue(String name, int column) throws UndefinedParameterError {
StringTokenizer reader = new StringTokenizer(name, ".");
String type = reader.nextToken();
if (type.equals("operator")) {
String opName = reader.nextToken();
Operator operator = getProcess().getOperator(opName);
if (operator != null) {
type = reader.nextToken();
if (type.equals("value")) {
String valueName = reader.nextToken();
Value value = operator.getValue(valueName);
if (value == null) {
logWarning("No such value in '" + name + "'");
return Double.NaN;
}
if (value.isNominal()) {
String valueString = value.getValue().toString();
SimpleDataTable table = (SimpleDataTable)getProcess().getDataTable(getName());
return table.mapString(column, valueString);
} else {
return ((Double)value.getValue()).doubleValue();
}
} else if (type.equals("parameter")) {
String parameterName = reader.nextToken();
ParameterType parameterType = operator.getParameterType(parameterName);
if (parameterType == null) {
logWarning("No such parameter in '" + name + "'");
return Double.NaN;
} else {
if (parameterType.isNumerical()) { // numerical
try {
return Double.parseDouble(operator.getParameter(parameterName).toString());
} catch (NumberFormatException e) {
logWarning("Cannot parse parameter value of '" + name + "'");
}
} else { // nominal
String value = parameterType.toString(operator.getParameter(parameterName));
SimpleDataTable table = (SimpleDataTable)getProcess().getDataTable(getName());
return table.mapString(column, value);
}
}
} else {
logWarning("Unknown token '" + type + "' in '" + name + "'");
}
} else {
logWarning("Unknown operator '" + opName + "' in '" + name + "'");
}
} else {
logWarning("Unknown token '" + type + "' in '" + name + "'");
}
return Double.NaN;
}
private String[] getValueNames() throws UndefinedParameterError {
List parameters = getParameterList(PARAMETER_LOG);
String [] valueNames = new String[parameters.size()];
Iterator i = parameters.iterator();
int j = 0;
while (i.hasNext()) {
Object[] parameter = (Object[]) i.next();
valueNames[j] = (String) parameter[1];
j++;
}
return valueNames;
}
public void processStarts() throws OperatorException {
super.processStarts();
List parameters = getParameterList(PARAMETER_LOG);
String columnNames[] = new String[parameters.size()];
Iterator i = parameters.iterator();
int j = 0;
while (i.hasNext()) {
Object[] parameter = (Object[]) i.next();
columnNames[j] = (String) parameter[0];
j++;
}
getProcess().addDataTable(new SimpleDataTable(getName(), columnNames));
}
public Class<?>[] getInputClasses() {
return new Class[0];
}
public Class<?>[] getOutputClasses() {
return new Class[0];
}
public IOObject[] apply() throws OperatorException {
DataTableRow row = fetchAllValues();
if (getParameterAsBoolean(PARAMETER_PERSISTENT)) {
writeOnline(row);
}
return new IOObject[] {};
}
private void writeOnline(DataTableRow row) throws UserError {
DataTable table = getProcess().getDataTable(getName());
File outputFile = null;
try {
outputFile = getParameterAsFile(PARAMETER_FILENAME);
PrintWriter out = new PrintWriter(new FileWriter(outputFile));
out.println("# Generated by " + getName() + "[" + getClass().getName() + "]");
for (int j = 0; j < table.getNumberOfColumns(); j++) {
out.print((j != 0 ? "\t" : "# ") + table.getColumnName(j));
}
out.println();
out.close();
out = new PrintWriter(new FileWriter(outputFile, true));
for (int j = 0; j < row.getNumberOfValues(); j++) {
out.print((j != 0 ? "\t" : "") + table.getValueAsString(row, j));
}
out.println();
out.close();
} catch (IOException e) {
throw new UserError(this, 303, outputFile, e.getMessage());
}
}
private DataTableRow fetchAllValues() throws UndefinedParameterError {
String[] valueNames = getValueNames();
double[] row = new double[valueNames.length];
for (int i = 0; i < valueNames.length; i++) {
double value = fetchValue(valueNames[i], i);
row[i] = value;
}
DataTableRow dataRow = new SimpleDataTableRow(row, null);
getProcess().getDataTable(getName()).add(dataRow);
return dataRow;
}
public void processFinished() throws OperatorException {
super.processFinished();
if (! getParameterAsBoolean(PARAMETER_PERSISTENT)) {
File file = null;
try {
file = getParameterAsFile(PARAMETER_FILENAME);
} catch (UndefinedParameterError e) {
// tries to determine a file for output writing
// if no file was specified --> do not write results in file
}
if (file != null) {
log("Writing data to '" + file.getName() + "'");
PrintWriter out = null;
try {
out = new PrintWriter(new FileWriter(file));
getProcess().getDataTable(getName()).write(out);
} catch (IOException e) {
throw new UserError(this, 303, file.getName(), e.getMessage());
} finally {
if (out != null)
out.close();
}
}
}
}
public List<ParameterType> getParameterTypes() {
List<ParameterType> types = super.getParameterTypes();
ParameterType type = new ParameterTypeFile(PARAMETER_FILENAME, "File to save the data to.", "log", true);
type.setExpert(false);
types.add(type);
type = new ParameterTypeList(PARAMETER_LOG, "List of key value pairs where the key is the column name and the value specifies the process value to log.", new ParameterTypeValue(PARAMETER_COLUMN_NAME, "operator.OPERATORNAME.[value|parameter].VALUE_NAME"));
type.setExpert(false);
types.add(type);
types.add(new ParameterTypeBoolean(PARAMETER_PERSISTENT, "Indicates if results should be written to file immediately", false));
return types;
}
}