/*
* Artificial Intelligence for Humans
* Volume 2: Nature Inspired Algorithms
* Java Version
* http://www.aifh.org
* http://www.jeffheaton.com
*
* Code repository:
* https://github.com/jeffheaton/aifh
*
* Copyright 2014 by Jeff Heaton
*
* 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 com.heatonresearch.aifh.examples.gp;
import com.heatonresearch.aifh.AIFHError;
import com.heatonresearch.aifh.genetic.trees.EvaluateTree;
import com.heatonresearch.aifh.genetic.trees.TreeGenomeNode;
import com.heatonresearch.aifh.randomize.GenerateRandom;
import java.util.Locale;
/**
* Evaluate expression. This class shows how to construct a tree evaluator that will evaluate the following
* operators:
* <p/>
* add, sub, div, mul, negative, power, sqrt, as well as variables.
* <p/>
* This class could easily be modified to support additional operators.
*/
public class EvaluateExpression extends EvaluateTree {
/**
* The opcode for add.
*/
public static final int OPCODE_ADD = 0;
/**
* The opcode for subtract.
*/
public static final int OPCODE_SUB = 1;
/**
* The opcode for divide.
*/
public static final int OPCODE_DIV = 2;
/**
* The opcode for multiply.
*/
public static final int OPCODE_MUL = 3;
/**
* The opcode for negative.
*/
public static final int OPCODE_NEG = 4;
/**
* The opcode for raise to the power.
*/
public static final int OPCODE_POWER = 5;
/**
* The opcode for square root.
*/
public static final int OPCODE_SQRT = 6;
/**
* The start of the constant and variable opcodes.
*/
public static final int OPCODE_VAR_CONST = 7;
/**
* The constant values.
*/
private double[] constValues;
/**
* The number of variables.
*/
private int varCount;
/**
* The constructor.
*
* @param rnd A random number generator.
* @param numConst The number of constants.
* @param numVar The number of variables.
* @param minConstValue The minimum amount for a constant.
* @param maxConstValue The maximum amount for a constant.
*/
public EvaluateExpression(GenerateRandom rnd, int numConst, int numVar, double minConstValue, double maxConstValue) {
this.constValues = new double[numConst];
this.varCount = numVar;
for (int i = 0; i < this.constValues.length; i++) {
this.constValues[i] = rnd.nextDouble(minConstValue, maxConstValue);
}
}
/**
* Construct an evaluator with 1 variable, and 100 constants ranging between (-5,5)
*
* @param rnd A random number generator.
*/
public EvaluateExpression(GenerateRandom rnd) {
this(rnd, 100, 1, -5, 5);
}
/**
* {@inheritDoc}
*/
@Override
public int determineChildCount(int opcode) {
switch (opcode) {
case OPCODE_ADD:
return 2;
case OPCODE_SUB:
return 2;
case OPCODE_DIV:
return 2;
case OPCODE_MUL:
return 2;
case OPCODE_NEG:
return 1;
case OPCODE_POWER:
return 2;
case OPCODE_SQRT:
return 1;
default:
return 0;
}
}
/**
* Get the text for an opcode.
*
* @param opcode The opcode.
* @return The text for the opcode.
*/
public String getOpcodeText(int opcode) {
switch (opcode) {
case OPCODE_NEG:
return "-";
case OPCODE_ADD:
return "+";
case OPCODE_SUB:
return "-";
case OPCODE_DIV:
return "/";
case OPCODE_MUL:
return "*";
case OPCODE_POWER:
return "^";
case OPCODE_SQRT:
return "sqrt";
default:
int index = opcode - OPCODE_VAR_CONST;
if (index >= (this.constValues.length + varCount)) {
throw new AIFHError("Invalid opcode: " + opcode);
}
if (index < this.varCount) {
return "" + ((char) ('a' + index));
} else {
return String.format(Locale.US, "%.8f", this.constValues[index - varCount]);
}
}
}
/**
* Display an expression as LISP (the programming language)
*
* @param node The root node.
* @return The LISP for the expression.
*/
public String displayExpressionLISP(TreeGenomeNode node) {
StringBuilder result = new StringBuilder();
if (determineChildCount(node.getOpcode()) == 0) {
result.append(getOpcodeText(node.getOpcode()));
} else {
result.append("(");
result.append(getOpcodeText(node.getOpcode()));
for (TreeGenomeNode child : node.getChildren()) {
result.append(" ");
result.append(displayExpressionLISP(child));
}
result.append(")");
}
return result.toString();
}
/**
* Display an expression as normal infix.
*
* @param node The root node.
* @return The infix string.
*/
public String displayExpressionNormal(TreeGenomeNode node) {
StringBuilder result = new StringBuilder();
if (determineChildCount(node.getOpcode()) == 0) {
result.append(getOpcodeText(node.getOpcode()));
} else {
int childCount = determineChildCount(node.getOpcode());
if (childCount == 0) {
result.append(getOpcodeText(node.getOpcode()));
} else {
String name = getOpcodeText(node.getOpcode());
if (name.length() > 1) {
result.append(name);
result.append("(");
boolean first = true;
for (TreeGenomeNode child : node.getChildren()) {
if (!first) {
result.append(",");
}
result.append(displayExpressionNormal(child));
first = false;
}
result.append(")");
} else {
result.append("(");
if (childCount == 2) {
result.append(displayExpressionNormal(node.getChildren().get(0)));
result.append(name);
result.append(displayExpressionNormal(node.getChildren().get(1)));
result.append(")");
} else {
result.append(name);
result.append(displayExpressionNormal(node.getChildren().get(0)));
result.append(")");
}
}
}
}
return result.toString();
}
/**
* {@inheritDoc}
*/
@Override
public int getVarConstOpcode() {
return OPCODE_VAR_CONST;
}
/**
* {@inheritDoc}
*/
@Override
public int getNumConst() {
return this.constValues.length;
}
/**
* {@inheritDoc}
*/
@Override
public int getNumVar() {
return this.varCount;
}
/**
* {@inheritDoc}
*/
@Override
public double evaluate(TreeGenomeNode node, double[] varValues) {
switch (node.getOpcode()) {
case OPCODE_NEG:
return -(evaluate(node.getChildren().get(0), varValues));
case OPCODE_ADD:
return evaluate(node.getChildren().get(0), varValues) + evaluate(node.getChildren().get(1), varValues);
case OPCODE_SUB:
return evaluate(node.getChildren().get(0), varValues) - evaluate(node.getChildren().get(1), varValues);
case OPCODE_DIV:
return evaluate(node.getChildren().get(0), varValues) / evaluate(node.getChildren().get(1), varValues);
case OPCODE_MUL:
return evaluate(node.getChildren().get(0), varValues) * evaluate(node.getChildren().get(1), varValues);
case OPCODE_POWER:
return Math.pow(evaluate(node.getChildren().get(0), varValues), evaluate(node.getChildren().get(1), varValues));
case OPCODE_SQRT:
return Math.sqrt(evaluate(node.getChildren().get(0), varValues));
default:
int index = node.getOpcode() - OPCODE_VAR_CONST;
if (index >= (this.constValues.length + varCount)) {
throw new AIFHError("Invalid opcode: " + node.getOpcode());
}
if (index < this.varCount) {
return varValues[index];
} else {
return this.constValues[index - this.varCount];
}
}
}
}