/* * Encog(tm) Core v3.4 - Java Version * http://www.heatonresearch.com/encog/ * https://github.com/encog/encog-java-core * Copyright 2008-2016 Heaton Research, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For more information on Heaton Research copyrights, licenses * and trademarks visit: * http://www.heatonresearch.com/copyright */ package org.encog.ml.prg; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.encog.ml.MLError; import org.encog.ml.MLRegression; import org.encog.ml.data.MLData; import org.encog.ml.data.MLDataSet; import org.encog.ml.data.basic.BasicMLData; import org.encog.ml.ea.exception.EACompileError; import org.encog.ml.ea.exception.EARuntimeError; import org.encog.ml.ea.genome.BasicGenome; import org.encog.ml.ea.genome.Genome; import org.encog.ml.prg.expvalue.ExpressionValue; import org.encog.ml.prg.expvalue.ValueType; import org.encog.ml.prg.extension.FunctionFactory; import org.encog.ml.prg.extension.StandardExtensions; import org.encog.ml.prg.train.PrgPopulation; import org.encog.ml.tree.traverse.tasks.TaskGetNodeIndex; import org.encog.ml.tree.traverse.tasks.TaskReplaceNode; import org.encog.parse.expression.common.ParseCommonExpression; import org.encog.parse.expression.common.RenderCommonExpression; import org.encog.parse.expression.epl.ParseEPL; import org.encog.parse.expression.epl.RenderEPL; import org.encog.parse.expression.rpn.RenderRPN; import org.encog.util.simple.EncogUtility; /** * Holds an Encog Programming Language (EPL) program. A Encog program is * internally represented as a tree structure. It can be rendered to a variety * of forms, such as RPN, common infix expressions, or Latex. The Encog * Workbench also allows display as a graphical tree. An Encog Program is both a * genome and phenome. No decoding is necessary. * * Every Encog Program has a context. The context is the same for every Encog * Program in a population. The context defines which opcodes should be used, as * well as the defined variables. * * The actual values for the variables are not stored in the context. Rather * they are stored in a variable holder. Each program usually has its own * variable holder, though it is possible to share. */ public class EncogProgram extends BasicGenome implements MLRegression, MLError { /** * The serial id. */ private static final long serialVersionUID = 1L; /** * Parse the specified program, or expression, and return the result. No * variables can be defined for this as a default context is used. The * result is returned as a boolean. * * @param str * The program expression. * @return The value the expression was evaluated to. */ public static boolean parseBoolean(final String str) { final EncogProgram holder = new EncogProgram(str); return holder.evaluate().toBooleanValue(); } /** * Parse the specified program, or expression, and return the result. No * variables can be defined for this as a default context is used. The * result is returned as a boolean. * * @param str * The program expression. * @return The value the expression was evaluated to. */ public static ExpressionValue parseExpression(final String str) { final EncogProgram holder = new EncogProgram(str); return holder.evaluate(); } /** * Parse the specified program, or expression, and return the result. No * variables can be defined for this as a default context is used. The * result is returned as a float. * * @param str * The program expression value. * @return The value the expression was evaluated to. */ public static double parseFloat(final String str) { final EncogProgram holder = new EncogProgram(str); return holder.evaluate().toFloatValue(); } /** * Parse the specified program, or expression, and return the result. No * variables can be defined for this as a default context is used. The * result is returned as a string. * * @param str * The program expression value. * @return The value the expression was evaluated to. */ public static String parseString(final String str) { final EncogProgram holder = new EncogProgram(str); return holder.evaluate().toStringValue(); } /** * The variables that will be used by this Encog program. */ private EncogProgramVariables variables = new EncogProgramVariables(); /** * The context that this Encog program executes in, the context is typically * shared with other Encog programs. */ private EncogProgramContext context = new EncogProgramContext(); /** * The root node of the program. */ private ProgramNode rootNode; /** * Holds extra data that might be needed by user extended opcodes. */ private Map<String,Object> extraData = new HashMap<String,Object>(); /** * Construct the Encog program and create a default context and variable * holder. Use all available opcodes. */ public EncogProgram() { this(new EncogProgramContext(), new EncogProgramVariables()); StandardExtensions.createAll(this.context); } /** * Construct the Encog program with the specified context, but create a new * variable holder. * * @param theContext * The context. */ public EncogProgram(final EncogProgramContext theContext) { this(theContext, new EncogProgramVariables()); } /** * Construct an Encog program using the specified context and variable * holder. * * @param theContext * The context. * @param theVariables * The variable holder. */ public EncogProgram(final EncogProgramContext theContext, final EncogProgramVariables theVariables) { this.context = theContext; this.variables = theVariables; defineVariablesFromContext(); } /** * Construct an Encog program using the specified expression, but create an * empty context and variable holder. * * @param expression * The expression. */ public EncogProgram(final String expression) { this(); compileExpression(expression); } /** * Construct an Encog program using the specified expression, but create an * empty context and variable holder. * * @param theContext The context for this program. * @param theExpression The expression. */ public EncogProgram(final EncogProgramContext theContext, final String theExpression) { this(theContext,new EncogProgramVariables()); defineVariablesFromContext(); compileExpression(theExpression); } /** * {@inheritDoc} */ @Override public double calculateError(final MLDataSet data) { return EncogUtility.calculateRegressionError(this, data); } /** * Compile the specified EPL into an actual program node structure, for * later execution. * * @param code The code to compile. * @return The root node. */ public ProgramNode compileEPL(final String code) { final ParseEPL parser = new ParseEPL(this); this.rootNode = parser.parse(code); return this.rootNode; } /** * Compile the specified expression. * * @param expression * The expression. * @return The program node that this was compiled into. */ public ProgramNode compileExpression(final String expression) { final ParseCommonExpression parser = new ParseCommonExpression(this); this.rootNode = parser.parse(expression); return this.rootNode; } public double compute(double... x) { MLData data = new BasicMLData(x); MLData result = compute(data); return result.getData(0); } /** * Compute the output from the input MLData. The individual values of the * input will be mapped to the variables defined in the context. The order * is the same between the input and the defined variables. The input will * be mapped to the appropriate types. Enums will use their ordinal number. * The result will be a single number MLData. * * @param input * The input to the program. * @return A single numer MLData. */ @Override public MLData compute(final MLData input) { if (input.size() != getInputCount()) { throw new EACompileError("Invalid input count, expected " + getInputCount() + ", but got " + input.size()); } for (int i = 0; i < input.size(); i++) { this.variables.setVariable(i, input.getData(i)); } final ExpressionValue v = this.rootNode.evaluate(); final VariableMapping resultMapping = getResultType(); final MLData result = new BasicMLData(1); boolean success = false; switch (resultMapping.getVariableType()) { case floatingType: if (v.isNumeric()) { result.setData(0, v.toFloatValue()); success = true; } break; case stringType: result.setData(0, v.toFloatValue()); success = true; break; case booleanType: if (v.isBoolean()) { result.setData(0, v.toBooleanValue() ? 1.0 : 0.0); success = true; } break; case intType: if (v.isNumeric()) { result.setData(0, v.toIntValue()); success = true; } break; case enumType: if (v.isEnum()) { result.setData(0, v.toIntValue()); success = true; } break; } if (!success) { throw new EARuntimeError("EncogProgram produced " + v.getExpressionType().toString() + " but " + resultMapping.getVariableType().toString() + " was expected."); } return result; } /** * {@inheritDoc} */ @Override public void copy(final Genome source) { // not needed, already a genome } /** * @return The string as a common "infix" expression. */ public String dumpAsCommonExpression() { final RenderCommonExpression render = new RenderCommonExpression(); return render.render(this); } /** * Execute the program and return the result. * * @return The result of running the program. */ public ExpressionValue evaluate() { return this.rootNode.evaluate(); } /** * Find the specified node by index. The tree is traversed to do this. This * is typically used to select a random node. * * @param index * The index being sought. * @return The program node found. */ public ProgramNode findNode(final int index) { return (ProgramNode) TaskGetNodeIndex.process(index, this.rootNode); } /** * @return The string as an EPL expression. EPL is the format that * EncogPrograms are usually persisted as. */ public String generateEPL() { final RenderEPL render = new RenderEPL(); return render.render(this); } /** * @return The program context. The program context may be shared over * multiple programs. */ public EncogProgramContext getContext() { return this.context; } /** * @return The function factory from the context. */ public FunctionFactory getFunctions() { return this.context.getFunctions(); } /** * {@inheritDoc} */ @Override public int getInputCount() { return this.variables.size(); } /** * {@inheritDoc} */ @Override public int getOutputCount() { return 1; } /** * @return The variable mapping for the result type. This is obtained from * the context. */ private VariableMapping getResultType() { return this.context.getResult(); } /** * @return The return type, from the context. */ public ValueType getReturnType() { return this.context.getResult().getVariableType(); } /** * @return The root node of the program. */ public ProgramNode getRootNode() { return this.rootNode; } /** * @return The variable holder. */ public EncogProgramVariables getVariables() { return this.variables; } /** * Replace the specified node with another. * * @param replaceThisNode * The node to replace. * @param replaceWith * The node that is replacing that node. */ public void replaceNode(final ProgramNode replaceThisNode, final ProgramNode replaceWith) { if (replaceThisNode == this.rootNode) { this.rootNode = replaceWith; } else { TaskReplaceNode .process(this.rootNode, replaceThisNode, replaceWith); } } /** * Select a random variable from the defined variables. * * @param rnd * A random number generator. * @param desiredTypes * The desired types that the variable can be. * @return The index of the defined variable, or -1 if unable to define. */ public int selectRandomVariable(final Random rnd, final List<ValueType> desiredTypes) { List<VariableMapping> selectionSet = this.context .findVariablesByTypes(desiredTypes); if (selectionSet.size() == 0 && desiredTypes.contains(ValueType.intType)) { final List<ValueType> floatList = new ArrayList<ValueType>(); floatList.add(ValueType.floatingType); selectionSet = this.context.findVariablesByTypes(floatList); } if (selectionSet.size() == 0) { return -1; } final VariableMapping selected = selectionSet.get(rnd .nextInt(selectionSet.size())); return getContext().getDefinedVariables().indexOf(selected); } /** * Set the root node for the program. * * @param theRootNode * The new root node. */ public void setRootNode(final ProgramNode theRootNode) { this.rootNode = theRootNode; } /** * {@inheritDoc} */ @Override public int size() { return this.rootNode.size(); } /** * {@inheritDoc} */ @Override public String toString() { final RenderRPN render = new RenderRPN(); final String code = render.render(this); final StringBuilder result = new StringBuilder(); result.append("[EncogProgram: size="); result.append(size()); result.append(", score="); result.append(getScore()); result.append(",code="); result.append(code); result.append("]"); return result.toString(); } /** * Get extra data that might be needed by user extended opcodes. * @param name The name the data was stored under. * @return The extra data. */ public Object getExtraData(final String name) { return this.extraData.get(name); } /** * Set extra data that might be needed by extensions. * @param name The name of the data stored. * @param value The data. */ public void setExtraData(final String name, final Object value) { this.extraData.put(name, value); } /** * Define any additional variables from context. */ private void defineVariablesFromContext() { // define variables for (final VariableMapping v : this.context.getDefinedVariables()) { if(!this.variables.variableExists(v.getName())) { this.variables.defineVariable(v); } } } }