/*
  Copyright 2012 by Uday Kamath, Sean Luke, and George Mason University
  Licensed under the Academic Free License version 3.0
  See the file "LICENSE" for more information
*/

package ec.gp.breed;

import ec.*;

import ec.util.*;
import ec.gp.*;
import ec.gp.koza.*;
import java.util.*;

/**
 * SizeFairCrossover works similarly to one written in the paper
 * "Size Fair and Homologous Tree Genetic Programming Crossovers" by Langdon (1998). 
 
 * 

SizeFairCrossover tries tries times to find a tree * that has at least one fair size node based on size fair or homologous * implementation. If it cannot * find a valid tree in tries times, it gives up and simply * copies the individual. *

This pipeline typically produces up to 2 new individuals (the two newly- * swapped individuals) per produce(...) call. If the system only * needs a single individual, the pipeline will throw one of the * new individuals away. The user can also have the pipeline always * throw away the second new individual instead of adding it to the population. * In this case, the pipeline will only typically * produce 1 new individual per produce(...) call.

Typical Number of Individuals Produced Per produce(...) call
2 * minimum typical number of individuals produced by each source, unless tossSecondParent is set, in which case it's simply the minimum typical number.

Number of Sources
2

Parameters

base.tries
int >= 1
(number of times to try finding valid pairs of nodes)
base.maxdepth
int >= 1
(maximum valid depth of a crossed-over subtree)
base.tree.0
0 < int < (num trees in individuals), if exists
(first tree for the crossover; if parameter doesn't exist, tree is picked at random)
base.tree.1
0 < int < (num trees in individuals), if exists
(second tree for the crossover; if parameter doesn't exist, tree is picked at random. This tree must have the same GPTreeConstraints as tree.0, if tree.0 is defined.)
base.ns.n
classname, inherits and != GPNodeSelector,
or String same
(GPNodeSelector for parent n (n is 0 or 1) If, for ns.1 the value is same, then ns.1 a copy of whatever ns.0 is. Note that the default version has no n)
base.toss
bool = true or false (default)/td>
(after crossing over with the first new individual, should its second sibling individual be thrown away instead of adding it to the population?)
base.homologous
bool = true or false (default)/td>
(Is the implementation homologous (as opposed to size-fair)?)

Default Base
gp.breed.size-fair

Parameter bases

base.ns.n
nodeselectn (n is 0 or 1)
* @author Uday Kamath and Sean Luke * @version 1.0 */ public class SizeFairCrossoverPipeline extends GPBreedingPipeline { private static final long serialVersionUID = 1; public static final String P_NUM_TRIES = "tries"; public static final String P_MAXDEPTH = "maxdepth"; public static final String P_SIZEFAIR = "size-fair"; public static final String P_TOSS = "toss"; public static final String P_HOMOLOGOUS = "homologous"; public static final int INDS_PRODUCED = 2; public static final int NUM_SOURCES = 2; /** How the pipeline selects a node from individual 1 */ public GPNodeSelector nodeselect1; /** How the pipeline selects a node from individual 2 */ public GPNodeSelector nodeselect2; /** Is the first tree fixed? If not, this is -1 */ public int tree1; /** Is the second tree fixed? If not, this is -1 */ public int tree2; /** How many times the pipeline attempts to pick nodes until it gives up. */ public int numTries; /** * The deepest tree the pipeline is allowed to form. Single terminal trees * are depth 1. */ public int maxDepth; /** Should the pipeline discard the second parent after crossing over? */ public boolean tossSecondParent; /** Temporary holding place for parents */ public GPIndividual parents[]; public boolean homologous; public SizeFairCrossoverPipeline() { parents = new GPIndividual[2]; } public Parameter defaultBase() { return GPBreedDefaults.base().push(P_SIZEFAIR); } public int numSources() { return NUM_SOURCES; } public Object clone() { SizeFairCrossoverPipeline c = (SizeFairCrossoverPipeline) (super.clone()); // deep-cloned stuff c.nodeselect1 = (GPNodeSelector) (nodeselect1.clone()); c.nodeselect2 = (GPNodeSelector) (nodeselect2.clone()); c.parents = (GPIndividual[]) parents.clone(); return c; } public void setup(final EvolutionState state, final Parameter base) { super.setup(state, base); Parameter def = defaultBase(); Parameter p = base.push(P_NODESELECTOR).push("0"); Parameter d = def.push(P_NODESELECTOR).push("0"); nodeselect1 = (GPNodeSelector) (state.parameters.getInstanceForParameter(p, d, GPNodeSelector.class)); nodeselect1.setup(state, p); p = base.push(P_NODESELECTOR).push("1"); d = def.push(P_NODESELECTOR).push("1"); if (state.parameters.exists(p, d) && state.parameters.getString(p, d).equals(V_SAME)) { // can't just copy it this time; the selectors // use internal caches. So we have to clone it no matter what nodeselect2 = (GPNodeSelector) (nodeselect1.clone()); } else { nodeselect2 = (GPNodeSelector) (state.parameters.getInstanceForParameter(p, d, GPNodeSelector.class)); nodeselect2.setup(state, p); } numTries = state.parameters.getInt(base.push(P_NUM_TRIES), def.push(P_NUM_TRIES), 1); if (numTries == 0) state.output.fatal("GPCrossover Pipeline has an invalid number of tries (it must be >= 1).", base.push(P_NUM_TRIES), def.push(P_NUM_TRIES)); maxDepth = state.parameters.getInt(base.push(P_MAXDEPTH), def.push(P_MAXDEPTH), 1); if (maxDepth == 0) state.output.fatal("GPCrossover Pipeline has an invalid maximum depth (it must be >= 1).", base.push(P_MAXDEPTH), def.push(P_MAXDEPTH)); tree1 = TREE_UNFIXED; if (state.parameters.exists(base.push(P_TREE).push("" + 0), def.push(P_TREE).push("" + 0))) { tree1 = state.parameters.getInt(base.push(P_TREE).push("" + 0), def.push(P_TREE).push("" + 0), 0); if (tree1 == -1) state.output.fatal("Tree fixed value, if defined, must be >= 0"); } tree2 = TREE_UNFIXED; if (state.parameters.exists(base.push(P_TREE).push("" + 1), def.push(P_TREE).push("" + 1))) { tree2 = state.parameters.getInt(base.push(P_TREE).push("" + 1), def.push(P_TREE).push("" + 1), 0); if (tree2 == -1) state.output.fatal("Tree fixed value, if defined, must be >= 0"); } tossSecondParent = state.parameters.getBoolean(base.push(P_TOSS), def.push(P_TOSS), false); if(state.parameters.exists(base.push(P_HOMOLOGOUS), null)) { //get the parameter homologous = state.parameters.getBoolean(base.push(P_HOMOLOGOUS), null, false); } } /** * Returns 2 * minimum number of typical individuals produced by any * sources, else 1* minimum number if tossSecondParent is true. */ public int typicalIndsProduced() { return (tossSecondParent ? minChildProduction() : minChildProduction() * 2); } /** Returns true if inner1 can feasibly be swapped into inner2's position. */ public boolean verifyPoints(final GPInitializer initializer, final GPNode inner1, final GPNode inner2) { // first check to see if inner1 is swap-compatible with inner2 // on a type basis if (!inner1.swapCompatibleWith(initializer, inner2)) return false; // next check to see if inner1 can fit in inner2's spot if (inner1.depth() + inner2.atDepth() > maxDepth) return false; // checks done! return true; } public int produce(final int min, final int max, final int start, final int subpopulation, final Individual[] inds, final EvolutionState state, final int thread) { // how many individuals should we make? int n = typicalIndsProduced(); if (n < min) n = min; if (n > max) n = max; // should we bother? if (!state.random[thread].nextBoolean(likelihood)) return reproduce(n, start, subpopulation, inds, state, thread, true); // DO produce children from source -- we've not done so already GPInitializer initializer = ((GPInitializer) state.initializer); for (int q = start; q < n + start; /* no increment */) // keep on going until we're filled up { // grab two individuals from our sources if (sources[0] == sources[1]) // grab from the same source sources[0].produce(2, 2, 0, subpopulation, parents, state, thread); else // grab from different sources { sources[0].produce(1, 1, 0, subpopulation, parents, state, thread); sources[1].produce(1, 1, 1, subpopulation, parents, state, thread); } // at this point, parents[] contains our two selected individuals // are our tree values valid? if (tree1 != TREE_UNFIXED && (tree1 < 0 || tree1 >= parents[0].trees.length)) // uh oh state.output.fatal("GP Crossover Pipeline attempted to fix tree.0 to a value which was out of bounds of the array of the individual's trees. Check the pipeline's fixed tree values -- they may be negative or greater than the number of trees in an individual"); if (tree2 != TREE_UNFIXED && (tree2 < 0 || tree2 >= parents[1].trees.length)) // uh oh state.output.fatal("GP Crossover Pipeline attempted to fix tree.1 to a value which was out of bounds of the array of the individual's trees. Check the pipeline's fixed tree values -- they may be negative or greater than the number of trees in an individual"); int t1 = 0; int t2 = 0; if (tree1 == TREE_UNFIXED || tree2 == TREE_UNFIXED) { do // pick random trees -- their GPTreeConstraints must be the same { if (tree1 == TREE_UNFIXED) if (parents[0].trees.length > 1) t1 = state.random[thread].nextInt(parents[0].trees.length); else t1 = 0; else t1 = tree1; if (tree2 == TREE_UNFIXED) if (parents[1].trees.length > 1) t2 = state.random[thread].nextInt(parents[1].trees.length); else t2 = 0; else t2 = tree2; } while (parents[0].trees[t1].constraints(initializer) != parents[1].trees[t2].constraints(initializer)); } else { t1 = tree1; t2 = tree2; // make sure the constraints are okay if (parents[0].trees[t1].constraints(initializer) != parents[1].trees[t2].constraints(initializer)) // uh oh state.output.fatal("GP Crossover Pipeline's two tree choices are both specified by the user -- but their GPTreeConstraints are not the same"); } boolean res1 = false; boolean res2 = false; GPTree tree2 = parents[1].trees[t2]; // pick some nodes GPNode p1 = null; GPNode p2 = null; // lets walk on parent2 all nodes to get subtrees for each node, doing it once for O(N) and not O(N^2) // because depth etc are computed and not stored ArrayList nodeToSubtrees = new ArrayList(); // also HashMap for size to List() of nodes in that size for O(1) lookup HashMap sizeToNodes = new HashMap(); this.traverseTreeForDepth(tree2.child, nodeToSubtrees, sizeToNodes); // sort the ArrayList with comparator that sorts by subtrees Collections.sort(nodeToSubtrees, new Comparator() { public int compare(Object o1, Object o2) { NodeInfo node1 = (NodeInfo)o1; NodeInfo node2 = (NodeInfo)o2; int comparison = 0; if (node1.numberOfSubTreesBeneath > node2.numberOfSubTreesBeneath) comparison = 1; else if (node1.numberOfSubTreesBeneath < node2.numberOfSubTreesBeneath) comparison = -1; else if (node1.numberOfSubTreesBeneath == node2.numberOfSubTreesBeneath) comparison = 0; return comparison; } }); for (int x = 0; x < numTries; x++) { // pick a node in individual 1 p1 = nodeselect1.pickNode(state, subpopulation, thread, parents[0], parents[0].trees[t1]); // now lets find "similar" in parent 2 p2 = findFairSizeNode(nodeToSubtrees, sizeToNodes, p1, tree2, state, thread); // check for depth and swap-compatibility limits res1 = verifyPoints(initializer, p2, p1); // p2 can fill p1's spot -- order is important! if (n - (q - start) < 2 || tossSecondParent) res2 = true; else res2 = verifyPoints(initializer, p1, p2); // p1 can fill p2's spot -- order is important! // did we get something that had both nodes verified? // we reject if EITHER of them is invalid. This is what lil-gp // does. // Koza only has numTries set to 1, so it's compatible as well. if (res1 && res2) break; } // at this point, res1 AND res2 are valid, OR // either res1 OR res2 is valid and we ran out of tries, OR // neither res1 nor res2 is valid and we rand out of tries. // So now we will transfer to a tree which has res1 or res2 // valid, otherwise it'll just get replicated. This is // compatible with both Koza and lil-gp. // at this point I could check to see if my sources were breeding // pipelines -- but I'm too lazy to write that code (it's a little // complicated) to just swap one individual over or both over, // -- it might still entail some copying. Perhaps in the future. // It would make things faster perhaps, not requiring all that // cloning. // Create some new individuals based on the old ones -- since // GPTree doesn't deep-clone, this should be just fine. Perhaps we // should change this to proto off of the main species prototype, // but // we have to then copy so much stuff over; it's not worth it. GPIndividual j1 = (GPIndividual) (parents[0].lightClone()); GPIndividual j2 = null; if (n - (q - start) >= 2 && !tossSecondParent) j2 = (GPIndividual) (parents[1].lightClone()); // Fill in various tree information that didn't get filled in there j1.trees = new GPTree[parents[0].trees.length]; if (n - (q - start) >= 2 && !tossSecondParent) j2.trees = new GPTree[parents[1].trees.length]; // at this point, p1 or p2, or both, may be null. // If not, swap one in. Else just copy the parent. for (int x = 0; x < j1.trees.length; x++) { if (x == t1 && res1) // we've got a tree with a kicking cross // position! { j1.trees[x] = (GPTree) (parents[0].trees[x].lightClone()); j1.trees[x].owner = j1; j1.trees[x].child = parents[0].trees[x].child.cloneReplacing(p2, p1); j1.trees[x].child.parent = j1.trees[x]; j1.trees[x].child.argposition = 0; j1.evaluated = false; } // it's changed else { j1.trees[x] = (GPTree) (parents[0].trees[x].lightClone()); j1.trees[x].owner = j1; j1.trees[x].child = (GPNode) (parents[0].trees[x].child.clone()); j1.trees[x].child.parent = j1.trees[x]; j1.trees[x].child.argposition = 0; } } if (n - (q - start) >= 2 && !tossSecondParent) for (int x = 0; x < j2.trees.length; x++) { if (x == t2 && res2) // we've got a tree with a kicking // cross position! { j2.trees[x] = (GPTree) (parents[1].trees[x].lightClone()); j2.trees[x].owner = j2; j2.trees[x].child = parents[1].trees[x].child.cloneReplacing(p1, p2); j2.trees[x].child.parent = j2.trees[x]; j2.trees[x].child.argposition = 0; j2.evaluated = false; } // it's changed else { j2.trees[x] = (GPTree) (parents[1].trees[x].lightClone()); j2.trees[x].owner = j2; j2.trees[x].child = (GPNode) (parents[1].trees[x].child.clone()); j2.trees[x].child.parent = j2.trees[x]; j2.trees[x].child.argposition = 0; } } // add the individuals to the population inds[q] = j1; q++; if (q < n + start && !tossSecondParent) { inds[q] = j2; q++; } } return n; } /** * This method finds a node using the logic given in the langdon paper. * @param nodeToSubtrees For Tree of Parent2 all precomputed stats about depth,subtrees etc * @param sizeToNodes Quick lookup for LinkedList of size to Nodes * @param parent1SelectedNode Node selected in parent1 * @param tree2 Tree of parent2 * @param state Evolution State passed for getting access to Random Object of MersenneTwiser * @param thread thread number */ protected GPNode findFairSizeNode(ArrayList nodeToSubtrees, HashMap sizeToNodes, GPNode parent1SelectedNode, GPTree tree2, EvolutionState state, int thread) { GPNode selectedNode = null; // get the size of subtrees of parent1 int parent1SubTrees = parent1SelectedNode.numNodes(GPNode.NODESEARCH_NONTERMINALS); // the maximum length in mate we are looking for int maxmatesublen = (parent1SubTrees == 0) ? 0 : 2 * parent1SubTrees + 1; // lets see if for all lengths we have trees corresponding boolean[] mateSizeAvailable = new boolean[maxmatesublen + 1]; // initialize the array to false for (int i = 0; i < maxmatesublen; i++) mateSizeAvailable[i] = false; // check for ones we have for (int i = 0; i < nodeToSubtrees.size(); i++) { NodeInfo nodeInfo = (NodeInfo)nodeToSubtrees.get(i); // get the length of trees int subtree = nodeInfo.numberOfSubTreesBeneath; if (subtree <= maxmatesublen) mateSizeAvailable[subtree] = true; } // choose matesublen so mean size change=0 if possible int countOfPositives = 0; int countOfNegatives = 0; int sumOfPositives = 0; int sumOfNegatives = 0; int l; for (l = 1; l < parent1SubTrees; l++) if (mateSizeAvailable[l]) { countOfNegatives++; sumOfNegatives += parent1SubTrees - l; } for (l = parent1SubTrees + 1; l <= maxmatesublen; l++) if (mateSizeAvailable[l]) { countOfPositives++; sumOfPositives += l - parent1SubTrees; } // if they are missing use the same int mateSublengthSelected = 0; if (sumOfPositives == 0 || sumOfNegatives == 0) { //if so then check if mate has the length and use that if(mateSizeAvailable[parent1SubTrees]) { mateSublengthSelected = parent1SubTrees; } //else we go with zero } else { // probability of same is dependent on do we find same sub trees // else 0.0 double pzero = (mateSizeAvailable[parent1SubTrees]) ? 1.0 / parent1SubTrees : 0.0; // positive probability double ppositive = (1.0 - pzero) / (countOfPositives + ((double) (countOfNegatives * sumOfPositives) / (sumOfNegatives))); // negative probability double pnegative = (1.0 - pzero) / (countOfNegatives + ((double) (countOfPositives * sumOfNegatives) / (sumOfPositives))); // total probability, just for making sure math is right ;-) double total = countOfNegatives * pnegative + pzero + countOfPositives * ppositive; // putting an assert for floating point calculations, similar to what langdon does // assert(total<1.01&&total>.99); // now create a Roulette Wheel RouletteWheelSelector wheel = new RouletteWheelSelector(maxmatesublen); // add probabilities to the wheel // all below the length of parent node get pnegative // all above get ppositive and one on node gets pzero for (l = 1; l < parent1SubTrees; l++) if (mateSizeAvailable[l]) wheel.add(pnegative, l); if (mateSizeAvailable[parent1SubTrees]) wheel.add(pzero, parent1SubTrees); for (l = parent1SubTrees + 1; l <= maxmatesublen; l++) if (mateSizeAvailable[l]) wheel.add(ppositive, l); // spin the wheel mateSublengthSelected = wheel.roulette(state, thread); } // now we have length chosen, but there can be many nodes with that // LinkedList listOfNodes = (LinkedList)(sizeToNodes.get(Integer.valueOf(mateSublengthSelected))); if(listOfNodes == null) { state.output. fatal("In SizeFairCrossoverPipeline, nodes for tree length " + mateSublengthSelected + " is null, indicates some serious error"); } // in size fair we choose the elements at random for given length int chosenNode = 0; // if using fair size get random from the list if(!homologous) { chosenNode = state.random[thread].nextInt(listOfNodes.size()); } // if homologous else { if(listOfNodes.size() > 1) { GPInitializer initializer = ((GPInitializer) state.initializer); int currentMinDistance = Integer.MAX_VALUE; for(int i =0 ; i< listOfNodes.size(); i++) { // get the GP node GPNode selectedMateNode = ((NodeInfo)listOfNodes.get(i)).node; // now lets traverse selected and parent 1 to see divergence GPNode currentMateNode = selectedMateNode; GPNode currentParent1Node = parent1SelectedNode; // found a match? boolean foundAMatchInAncestor = false; int distance =0; while(currentMateNode.parent != null && currentMateNode.parent instanceof GPNode && currentParent1Node.parent != null && currentParent1Node.parent instanceof GPNode && !foundAMatchInAncestor) { GPNode parent1 = (GPNode)currentParent1Node.parent; GPNode parent2 = (GPNode)currentMateNode.parent; // if there is match between compatibility of parents break if(parent1.swapCompatibleWith(initializer, parent2)) { foundAMatchInAncestor = true; break; } else { // need to go one level above of both currentMateNode = parent2; currentParent1Node = parent1; //increment the distance distance = distance +1; } } // find the one with least distance if(distance < currentMinDistance) { currentMinDistance = distance; chosenNode = i; } } } // else take the first node, no choice } NodeInfo nodeInfoSelected = (NodeInfo)listOfNodes.get(chosenNode); selectedNode = nodeInfoSelected.node; return selectedNode; } /** * Recursively travel the tree so that depth and subtree below are computed * only once and can be reused later. * * @param node * @param nodeToDepth */ public void traverseTreeForDepth(GPNode node, ArrayList nodeToDepth, HashMap sizeToNodes) { GPNode[] children = node.children; NodeInfo nodeInfo = new NodeInfo(node, node.numNodes(GPNode.NODESEARCH_NONTERMINALS)); nodeToDepth.add(nodeInfo); // check to see if there is list in map for that size LinkedList listForSize = (LinkedList)(sizeToNodes.get(Integer.valueOf(nodeInfo.numberOfSubTreesBeneath))); if (listForSize == null) { listForSize = new LinkedList(); sizeToNodes.put(new Integer(nodeInfo.numberOfSubTreesBeneath), listForSize); } // add it to the list no matter what listForSize.add(nodeInfo); // recurse if (children.length > 0) { for (int i = 0; i < children.length; i++) { traverseTreeForDepth(children[i], nodeToDepth, sizeToNodes); } } } /** * Inner class to do a quick Roulette Wheel Selection * */ static class RouletteWheelSelector { int[] length; double[] probability; int currentIndex = 0; int maxLength = 0; RouletteWheelSelector(int size) { length = new int[size]; probability =new double[size]; } public void add(double currentProbability, int currentLength) { length[currentIndex] = currentLength; probability[currentIndex] = currentProbability; currentIndex = currentIndex +1; if(currentLength > maxLength) maxLength = currentLength; } public int roulette(EvolutionState state, int thread) { int winner = 0; int selectedLength = 0; // accumulate for (int i = 1; i < currentIndex; i++) { probability[i] += probability[i-1]; } int bot = 0; // binary chop search int top = currentIndex - 1; double f = state.random[thread].nextDouble() * probability[top]; for(int loop =0; loop< 20; loop++) { int index = (top + bot) / 2; if (index > 0 && f < probability[index - 1]) top = index - 1; else if (f > probability[index]) bot = index + 1; else { if (f == probability[index] && index + 1 < currentIndex) winner = index + 1; else winner = index; break; } } // check for bounds if(winner < 0 || winner >= currentIndex) { state.output.fatal("roulette() method winner " + winner + " out of range 0..." + (currentIndex-1)); winner=0; //safe default } if(length[winner] < 1 || length[winner] > maxLength) { state.output.fatal("roulette() method " + length[winner] + " is out of range 1..." + maxLength); // range is specified on creation return maxLength; //safe default } selectedLength = length[winner]; return selectedLength; } } /** *Used for O(1) information of number of subtrees * */ static class NodeInfo { // numberOfSubTrees beneath int numberOfSubTreesBeneath; // actual node GPNode node; public NodeInfo(GPNode node, int numberOfSubtrees) { this.node = node; this.numberOfSubTreesBeneath = numberOfSubtrees; } public void setSubtrees(int totalSubtrees) { this.numberOfSubTreesBeneath = totalSubtrees; } public int getSubtrees() { return numberOfSubTreesBeneath; } public GPNode getNode() { return node; } } }

Ads help maintain this website.