/* Copyright 2006 by Sean Luke Licensed under the Academic Free License version 3.0 See the file "LICENSE" for more information */ package ec.gp; import ec.*; import java.io.*; import ec.util.*; import ec.EvolutionState; /* * GPNode.java * * Created: Fri Aug 27 17:14:12 1999 * By: Sean Luke */ /** * GPNode is a GPNodeParent which is the abstract superclass of * all GP function nodes in trees. GPNode contains quite a few functions * for cloning subtrees in special ways, counting the number of nodes * in subtrees in special ways, and finding specific nodes in subtrees. * * GPNode's lightClone() method does not clone its children (it copies the * array, but that's it). If you want to deep-clone a tree or subtree, you * should use one of the cloneReplacing(...) methods instead. * *

GPNodes contain a number of important items: *

* *

In addition to serialization for checkpointing, GPNodes may read and write themselves to streams in three ways. * *

Parameters
base.nc
String
(name of the node constraints for the GPNode)

Default Base
gp.node * * @author Sean Luke * @version 1.0 */ public abstract class GPNode implements GPNodeParent, Prototype { public static final String P_NODE = "node"; public static final String P_NODECONSTRAINTS = "nc"; public static final String GPNODEPRINTTAB = " "; public static final int MAXPRINTBYTES = 40; public static final int NODESEARCH_ALL = 0; public static final int NODESEARCH_TERMINALS = 1; public static final int NODESEARCH_NONTERMINALS = 2; public static final int NODESEARCH_CUSTOM = 3; public static final int SITUATION_NEWIND = 0; public static final int SITUATION_MUTATION = 1; // beats me if Java compilers will take advantage of the int->byte shortening. // They may want everything aligned, in which case they may buffer the object // anyway, hope not! /** The GPNode's parent. 4 bytes. :-( But it really helps simplify breeding. */ public GPNodeParent parent; public GPNode children[]; /** The argument position of the child in its parent. This is a byte to save space (GPNode is the critical object space-wise) -- besides, how often do you have 256 children? You can change this to a short or int easily if you absolutely need to. It's possible to eliminate even this and have the child find itself in its parent, but that's an O(children[]) operation, and probably not inlinable, so I figure a byte is okay. */ public byte argposition; /** The GPNode's constraints. This is a byte to save space -- how often do you have 256 different GPNodeConstraints? Well, I guess it's not infeasible. You can increase this to an int without much trouble. You typically shouldn't access the constraints through this variable -- use the constraints(state) method instead. */ public byte constraints; /* Returns the GPNode's constraints. A good JIT compiler should inline this. */ public final GPNodeConstraints constraints(final GPInitializer initializer) { return initializer.nodeConstraints[constraints]; } /** The default base for GPNodes -- defined even though GPNode is abstract so you don't have to in subclasses. */ public Parameter defaultBase() { return GPDefaults.base().push(P_NODE); } /** You ought to override this method to check to make sure that the constraints are valid as best you can tell. Things you might check for:

You can't check for everything, of course, but you might try some obvious checks for blunders. The default version of this method is empty for now, but you should still call super.checkConstraints(state) just to be certain. The ultimate caller of this method must guarantee that he will eventually call state.output.exitIfErrors(), so you can freely use state.output.error instead of state.output.fatal(), which will help a lot. Warning: this method may get called more than once. */ public void checkConstraints(final EvolutionState state, final int tree, final GPIndividual typicalIndividual, final Parameter individualBase) { } /** Sets up a prototypical GPNode with those features all nodes of that prototype share, and nothing more. So no filled-in children, no argposition, no parent. Yet. This must be called after the GPTypes and GPNodeConstraints have been set up. Presently they're set up in GPInitializer, which gets called before this does, so we're safe. You should override this if you need to load some special features on a per-function basis. Note that base hangs off of a function set, so this method may get called for different instances in the same GPNode class if they're being set up as prototypes for different GPFunctionSets. If you absolutely need some global base, then you should use something hanging off of GPDefaults.base(). The ultimate caller of this method must guarantee that he will eventually call state.output.exitIfErrors(), so you can freely use state.output.error instead of state.output.fatal(), which will help a lot. */ public void setup(final EvolutionState state, final Parameter base) { Parameter def = defaultBase(); // determine my constraints -- at this point, the constraints should have been loaded. String s = state.parameters.getString(base.push(P_NODECONSTRAINTS), def.push(P_NODECONSTRAINTS)); if (s==null) state.output.fatal("No node constraints are defined for the GPNode " + toStringForError(),base.push(P_NODECONSTRAINTS), def.push(P_NODECONSTRAINTS)); else constraints = GPNodeConstraints.constraintsFor(s,state).constraintNumber; // The number of children is determined by the constraints. Though // for some special versions of GPNode, we may have to enforce certain // rules, checked in children versions of setup(...) GPNodeConstraints constraintsObj = constraints(((GPInitializer)state.initializer)); int len = constraintsObj.childtypes.length; if (len == 0) children = constraintsObj.zeroChildren; else children = new GPNode[len]; } /** Returns the argument type of the slot that I fit into in my parent. If I'm the root, returns the treetype of the GPTree. */ public final GPType parentType(final GPInitializer initializer) { if (parent instanceof GPNode) return ((GPNode)parent).constraints(initializer).childtypes[argposition]; else // it's a tree root return ((GPTree)parent).constraints(initializer).treetype; } /** Verification of validity of the node in the tree -- strictly for debugging purposes only */ final int verify(EvolutionState state, GPFunctionSet set, int index) { if (!(state.initializer instanceof GPInitializer)) { state.output.error("" + index + ": Initializer is not a GPInitializer"); return index+1; } GPInitializer initializer = (GPInitializer)(state.initializer); // 1. Is the parent and argposition right? if (parent == null) { state.output.error("" + index + ": null parent"); return index+1; } if (argposition < 0) { state.output.error("" + index + ": negative argposition"); return index+1; } if (parent instanceof GPTree && ((GPTree)parent).child != this) { state.output.error("" + index + ": I think I am a root node, but my GPTree does not think I am a root node"); return index+1; } if (parent instanceof GPTree && argposition != 0) { state.output.error("" + index + ": I think I am a root node, but my argposition is not 0"); return index+1; } if (parent instanceof GPNode && argposition >= ((GPNode)parent).children.length) { state.output.error("" + index + ": argposition outside range of parent's children array"); return index+1; } if (parent instanceof GPNode && ((GPNode)parent).children[argposition] != this) { state.output.error("" + index + ": I am not found in the provided argposition ("+argposition+") of my parent's children array"); return index+1; } // 2. Are the parents and argpositions right for my kids? [need to double check] if (children==null) { state.output.error("" + index + ": Null Children Array"); return index+1; } for(int x=0;x= initializer.numNodeConstraints) { state.output.error("" + index + ": Preposterous node constraints (" + constraints + ")"); return index+1; } // 4. Am I swap-compatable with my parent? if (parent instanceof GPNode && !constraints(initializer).returntype.compatibleWith(initializer, ((GPNode)(parent)).constraints(initializer).childtypes[argposition])) { state.output.error("" + index + ": Incompatable GP type between me and my parent"); return index+1; } if (parent instanceof GPTree && !constraints(initializer).returntype.compatibleWith(initializer, ((GPTree)(parent)).constraints(initializer).treetype)) { state.output.error("" + index + ": I am root, but incompatable GP type between me and my tree return type"); return index+1; } // 5. Is my class in the GPFunctionSet? GPNode[] nodes = set.nodesByArity[constraints(initializer).returntype.type][children.length]; boolean there = false; for(int x=0;x0)) ? 1 : 0); } /** Returns the depth of the tree, which is a value >= 1. O(n). */ public int depth() { int d=0; int newdepth; for(int x=0;xd) d = newdepth; } return d + 1; } /** Returns the path length of the tree, which is the sum of all paths from all nodes to the root. O(n). */ public int pathLength(int nodesearch) { return pathLength(NODESEARCH_ALL, 0); } int pathLength(int nodesearch, int currentDepth) { int sum = currentDepth; if (nodesearch == NODESEARCH_NONTERMINALS && children.length==0 || // I'm a leaf, don't include me nodesearch == NODESEARCH_TERMINALS && children.length > 0) // I'm a nonleaf, don't include me sum = 0; for(int x=0;x= 0. O(ln n) avg.*/ public int atDepth() { // -- new code, no need for recursion GPNodeParent cparent = parent; int count=0; while(cparent!=null && cparent instanceof GPNode) { count++; cparent = ((GPNode)(cparent)).parent; } return count; /* // -- old code if (parent==null) return 0; if (!(parent instanceof GPNode)) // found the root! return 0; else return 1 + ((GPNode)parent).atDepth(); */ } /** Returns the p'th node, constrained by nodesearch, in the subtree for which this GPNode is root. Use numNodes(nodesearch) to determine the total number. Or if you used numNodes(g), then when nodesearch == NODESEARCH_CUSTOM, g.test(...) is used as the constraining predicate. p ranges from 0 to this number minus 1. O(n). The resultant node is returned in g.*/ public int nodeInPosition(int p, final GPNodeGatherer g, final int nodesearch) { // am I of the type I'm looking for? if (nodesearch==NODESEARCH_ALL || (nodesearch==NODESEARCH_TERMINALS && children.length==0) || (nodesearch==NODESEARCH_NONTERMINALS && children.length>0) || (nodesearch==NODESEARCH_CUSTOM && g.test(this))) { // is the count now at 0? Is it me? if (p==0) { g.node = this; return -1; // found it } // if it's not me, drop the count by 1 else p--; } // regardless, check my children if I've not returned by now for(int x=0;xnot a copy of newSubtree). The result has everything set except for the root node's parent and argposition. */ public final GPNode cloneReplacingNoSubclone(final GPNode newSubtree, final GPNode oldSubtree) { if (this==oldSubtree) { return newSubtree; } else { GPNode newnode = (GPNode)(lightClone()); for(int x=0;x= 0) return newSubtrees[candidate].cloneReplacing(newSubtrees,oldSubtrees); else { GPNode newnode = (GPNode)(lightClone()); for(int x=0;xnot check for this, and if they are not the result is undefined. */ public final GPNode cloneReplacingAtomic(final GPNode newNode, final GPNode oldNode) { int numArgs; GPNode curnode; if (this==oldNode) { numArgs = Math.max(newNode.children.length,children.length); curnode = newNode; } else { numArgs = children.length; curnode = (GPNode)lightClone(); } // populate for(int x=0;xnot check for this, and if they are not the result is undefined. */ public final GPNode cloneReplacingAtomic(final GPNode[] newNodes, final GPNode[] oldNodes) { int numArgs; GPNode curnode; int found = -1; for(int x=0;x -1) { numArgs = Math.max(newNodes[found].children.length, children.length); curnode = newNodes[found]; } else { numArgs = children.length; curnode = (GPNode)lightClone(); } // populate for(int x=0;x>> 31 ) ^ children[x].rootedTreeHashCode(); return hash; } /** Returns true if I am the "genetically" identical to this node, and our children arrays are the same length, though we may have different parents and children. The default form of this method simply calls the much weaker nodeEquivalentTo(node). You may need to override this to perform exact comparisons, if you're an ERC, ADF, or ADM for example. Here's an example of how nodeEquivalentTo(node) differs from nodeEquals(node): two ERCs, both of the same class, but one holding '1.23' and the other holding '2.45', which came from the same prototype node in the same function set. They should NOT be nodeEquals(...) but *should* be nodeEquivalent(...). */ public boolean nodeEquals(final GPNode node) { return nodeEquivalentTo(node); } /** Returns true if the two rooted trees are "genetically" equal, though they may have different parents. O(n). */ public boolean rootedTreeEquals(final GPNode node) { if (!nodeEquals(node)) return false; for (int x=0;x " + newprefix + ";\n"; } return body; } /** Produces the LaTeX code for a LaTeX tree of the subtree rooted at this node, using the epic and fancybox packages, as described in sections 10.5.2 (page 307) and 10.1.3 (page 278) of The LaTeX Companion, respectively. For this to work, the output of toStringForHumans() must not contain any weird latex characters, notably { or } or % or \, unless you know what you're doing. See the documentation for ec.gp.GPTree for information on how to take this code snippet and insert it into your LaTeX file. Note that this isn't particularly efficient and should only be used to generate occasional trees for display, not for storing individuals or sending them over networks. */ public String makeLatexTree() { if (children.length==0) return "\\gpbox{"+toStringForHumans()+"}"; String s = "\\begin{bundle}{\\gpbox{"+toStringForHumans()+"}}"; for(int x=0;xuseOperatorForm. Additionally, terminals will be printed out either in variable form -- a -- or in zero-argument function form -- a() -- depending on the setting of printTerminalsAsVariables. Note that this isn't particularly efficient and should only be used to generate occasional trees for display, not for storing individuals or sending them over networks. */ public String makeCTree(boolean parentMadeParens, boolean printTerminalsAsVariables, boolean useOperatorForm) { if (children.length==0) return (printTerminalsAsVariables ? toStringForHumans() : toStringForHumans() + "()"); else if (children.length==1) return toStringForHumans() + "(" + children[0].makeCTree(true, printTerminalsAsVariables, useOperatorForm) + ")"; else if (children.length==2 && useOperatorForm) return (parentMadeParens ? "" : "(") + children[0].makeCTree(false, printTerminalsAsVariables, useOperatorForm) + " " + toStringForHumans() + " " + children[1].makeCTree(false, printTerminalsAsVariables, useOperatorForm) + (parentMadeParens ? "" : ")"); else { String s = toStringForHumans() + "(" + children[0].makeCTree(true, printTerminalsAsVariables, useOperatorForm); for(int x = 1; x < children.length;x++) s = s + ", " + children[x].makeCTree(true, printTerminalsAsVariables, useOperatorForm); return s + ")"; } } /** Produces a tree for human consumption in Lisp form similar to that generated by printTreeForHumans(). Note that this isn't particularly efficient and should only be used to generate occasional trees for display, not for storing individuals or sending them over networks. */ public String makeLispTree() { if (children.length==0) return toStringForHumans(); else { String s = "(" + toStringForHumans(); for(int x=0;x0) { state.output.print(" (",verbosity,log); printbytes += 2; } else { state.output.print(" ",log); printbytes += 1; } printbytes += printNode(state,log); for (int x=0;x0) { state.output.print(")",log); printbytes += 1; } return printbytes; } /** Prints out the tree on a single line, with no ending \n, in a fashion that can be read in later by computer. O(n). Returns the number of bytes printed. You should call this method with printbytes == 0. */ public int printRootedTree(final EvolutionState state, final PrintWriter writer, int printbytes) { if (children.length>0) { writer.print(" ("); printbytes += 2; } else { writer.print(" "); printbytes += 1; } printbytes += printNode(state,writer); for (int x=0;x0) { writer.print(")"); printbytes += 1; } return printbytes; } /** Prints out the tree in a readable Lisp-like multi-line fashion. O(n). You should call this method with tablevel and printbytes == 0. No ending '\n' is printed. */ public int printRootedTreeForHumans(final EvolutionState state, final int log, int tablevel, int printbytes) { return printRootedTreeForHumans(state, log, Output.V_VERBOSE, tablevel, printbytes); } /** Prints out the tree in a readable Lisp-like multi-line fashion. O(n). You should call this method with tablevel and printbytes == 0. No ending '\n' is printed. @deprecated Verbosity no longer has an effect. */ public int printRootedTreeForHumans(final EvolutionState state, final int log, final int verbosity, int tablevel, int printbytes) { if (printbytes>MAXPRINTBYTES) { state.output.print("\n",log); tablevel++; printbytes = 0; for(int x=0;x0) { state.output.print(" (",log); printbytes += 2; } else { state.output.print(" ",log); printbytes += 1; } printbytes += printNodeForHumans(state,log); for (int x=0;x0) { state.output.print(")",log); printbytes += 1; } return printbytes; } /** Reads the node symbol, advancing the DecodeReturn to the first character in the string beyond the node symbol, and returns a new, empty GPNode of the appropriate class representing that symbol, else null if the node symbol is not of the correct type for your GPNode class. You may assume that initial whitespace has been eliminated. Generally should be case-SENSITIVE, unlike in Lisp. The default version usually works for "simple" function names, that is, not ERCs or other stuff where you have to encode the symbol. */ public GPNode readNode(DecodeReturn dret) { int len = dret.data.length(); // get my name String str2 = toString(); int len2 = str2.length(); if (dret.pos + len2 > len) // uh oh, not enough space return null; // check it out for(int x=0; x < len2 ; x++) if (dret.data.charAt(dret.pos + x) != str2.charAt(x)) return null; // looks good! Check to make sure that // the symbol's all there is if (dret.data.length() > dret.pos+len2) { char c = dret.data.charAt(dret.pos+len2); if (!Character.isWhitespace(c) && c != ')' && c != '(') // uh oh return null; } // we're happy! dret.pos += len2; return (GPNode)lightClone(); } public void writeRootedTree(final EvolutionState state,final GPType expectedType, final GPFunctionSet set, final DataOutput dataOutput) throws IOException { dataOutput.writeInt(children.length); boolean isTerminal = (children.length == 0); // identify the node GPNode[] gpfi = isTerminal ? set.terminals[expectedType.type] : set.nonterminals[expectedType.type]; int index=0; for( /*int index=0 */; index = len) state.output.fatal("Reading line " + linenumber + ": " + "Premature end of tree structure -- did you forget a close-parenthesis?\nThe tree was" + dret.data); // if I've found a ')', complain if (dret.data.charAt(dret.pos) == ')') { StringBuffer sb = new StringBuffer(dret.data); sb.setCharAt(dret.pos,REPLACEMENT_CHAR); dret.data = sb.toString(); state.output.fatal("Reading line " + linenumber + ": " + "Premature ')' which I have replaced with a '" + REPLACEMENT_CHAR + "', in tree:\n" + dret.data); } // determine if I'm a terminal or not if (dret.data.charAt(dret.pos) == '(') { isTerminal=false; dret.pos++; // strip following whitespace for( ; dret.pos < len && Character.isWhitespace(dret.data.charAt(dret.pos)) ; dret.pos++); } // check again if I'm out of space if (dret.pos >= len) state.output.fatal("Reading line " + linenumber + ": " + "Premature end of tree structure -- did you forget a close-parenthesis?\nThe tree was" + dret.data); // check again if I found a ')' if (dret.data.charAt(dret.pos) == ')') { StringBuffer sb = new StringBuffer(dret.data); sb.setCharAt(dret.pos,REPLACEMENT_CHAR); dret.data = sb.toString(); state.output.fatal("Reading line " + linenumber + ": " + "Premature ')' which I have replaced with a '" + REPLACEMENT_CHAR + "', in tree:\n" + dret.data); } // find that node! GPNode[] gpfi = isTerminal ? set.terminals[expectedType.type] : set.nonterminals[expectedType.type]; GPNode node = null; for(int x=0;x= len) state.output.fatal("Reading line " + linenumber + ": " + "Premature end of tree structure -- did you forget a close-parenthesis?\nThe tree was" + dret.data); if (dret.data.charAt(dret.pos) != ')') { if (dret.pos!=0) { StringBuffer sb = new StringBuffer(dret.data); sb.setCharAt(dret.pos,REPLACEMENT_CHAR); dret.data = sb.toString(); } else dret.data = "" + REPLACEMENT_CHAR + dret.data; state.output.fatal("Reading line " + linenumber + ": " + "A nonterminal node has too many arguments. I have put a '" + REPLACEMENT_CHAR + "' just before the offending argument.\n" + dret.data); } else dret.pos++; // get rid of the ')' } // return the node return node; } /** Evaluates the node with the given thread, state, individual, problem, and stack. Your random number generator will be state.random[thread]. The node should, as appropriate, evaluate child nodes with these same items passed to eval(...).

About input: input is special; it is how data is passed between parent and child nodes. If children "receive" data from their parent node when it evaluates them, they should receive this data stored in input. If (more likely) the parent "receives" results from its children, it should pass them an input object, which they'll fill out, then it should check this object for the returned value.

A tree is typically evaluated by dropping a GPData into the root. When the root returns, the resultant input should hold the return value.

In general, you should not be creating new GPDatas. If you think about it, in most conditions (excepting ADFs and ADMs) you can use and reuse input for most communications purposes between parents and children.

So, let's say that your GPNode function implements the boolean AND function, and expects its children to return return boolean values (as it does itself). You've implemented your GPData subclass to be, uh, BooleanData, which looks like *

public class BooleanData extends GPData 
        *    {
        *    public boolean result;
        *    public GPData copyTo(GPData gpd)
        *      {
        *      ((BooleanData)gpd).result = result;
        *      }
        *    }

...so, you might implement your eval(...) function as follows: *

public void eval(final EvolutionState state,
        *                     final int thread,
        *                     final GPData input,
        *                     final ADFStack stack,
        *                     final GPIndividual individual,
        *                     final Problem problem
        *    {
        *    BooleanData dat = (BooleanData)input;
        *    boolean x;
        *
        *    // evaluate the first child
        *    children[0].eval(state,thread,input,stack,individual,problem);
        *  
        *    // store away its result
        *    x = dat.result;
        *
        *    // evaluate the second child
        *    children[1].eval(state,thread,input,stack,individual,problem);
        *
        *    // return (in input) the result of the two ANDed
        *
        *    dat.result = dat.result && x;
        *    return;
        *    }
        
*/ public abstract void eval(final EvolutionState state, final int thread, final GPData input, final ADFStack stack, final GPIndividual individual, final Problem problem); }