From 02fd2bc7059da416937beb1abe67e5ca60379030 Mon Sep 17 00:00:00 2001 From: Eduardo Pedroni Date: Tue, 1 Apr 2014 23:00:53 +0100 Subject: Settings pane now actually controls the parameters, not much left to do. --- src/jcgp/backend/population/Chromosome.java | 335 ++++++++++++++++++++++++ src/jcgp/backend/population/Connection.java | 9 + src/jcgp/backend/population/Gene.java | 5 + src/jcgp/backend/population/Input.java | 29 ++ src/jcgp/backend/population/MutableElement.java | 34 +++ src/jcgp/backend/population/Node.java | 112 ++++++++ src/jcgp/backend/population/Output.java | 69 +++++ src/jcgp/backend/population/Population.java | 45 ++++ 8 files changed, 638 insertions(+) create mode 100644 src/jcgp/backend/population/Chromosome.java create mode 100644 src/jcgp/backend/population/Connection.java create mode 100644 src/jcgp/backend/population/Gene.java create mode 100644 src/jcgp/backend/population/Input.java create mode 100644 src/jcgp/backend/population/MutableElement.java create mode 100644 src/jcgp/backend/population/Node.java create mode 100644 src/jcgp/backend/population/Output.java create mode 100644 src/jcgp/backend/population/Population.java (limited to 'src/jcgp/backend/population') diff --git a/src/jcgp/backend/population/Chromosome.java b/src/jcgp/backend/population/Chromosome.java new file mode 100644 index 0000000..18ae9bb --- /dev/null +++ b/src/jcgp/backend/population/Chromosome.java @@ -0,0 +1,335 @@ +package jcgp.backend.population; + +import java.util.ArrayList; + +import jcgp.JCGP.Resources; +import jcgp.backend.exceptions.ParameterMismatchException; + +public class Chromosome { + + private Resources resources; + + private Input[] inputs; + private Node[][] nodes; + private Output[] outputs; + + private ArrayList activeNodes; + + private int fitness = 0; + private boolean recomputeActiveNodes = true; + + /** + * Initialise a chromosome with the specified parameters. Random valid connections + * are created. + * + * + */ + public Chromosome(Resources resources) { + // store a reference to the parameters + this.resources = resources; + + // allocate memory for all elements of the chromosome + instantiateElements(); + // set random connections so that the chromosome can be evaluated + initialiseConnections(); + } + + /** + * Copy constructor. + * + * Initialise a new chromosome with the exact same connections as a given instance of Chromosome. + * + * @param clone the chromosome to be copied + */ + public Chromosome(Chromosome clone) { + // store a reference to the parameters + this.resources = clone.getParameters(); + + // allocate memory for all elements of the chromosome + instantiateElements(); + // initialise all connections based on argument + copyConnections(clone); + } + + /** + * + */ + private void instantiateElements() { + inputs = new Input[(resources.getInt("inputs"))]; + for (int i = 0; i < (resources.getInt("inputs")); i++) { + inputs[i] = new Input(i); + } + + int arity = resources.getInt("arity"); + + // rows first + nodes = new Node[(resources.getInt("rows"))][(resources.getInt("columns"))]; + for (int r = 0; r < (resources.getInt("rows")); r++) { + for (int c = 0; c < (resources.getInt("columns")); c++) { + nodes[r][c] = new Node(this, r, c, arity); + } + } + outputs = new Output[(resources.getInt("outputs"))]; + for (int o = 0; o < (resources.getInt("outputs")); o++) { + outputs[o] = new Output(this, o); + } + } + + /** + * + */ + private void initialiseConnections() { + + int arity = resources.getInt("arity"); + + // initialise nodes - [rows][columns] + for (int r = 0; r < nodes.length; r++) { + for (int c = 0; c < nodes[r].length; c++) { + Connection[] connections = new Connection[arity]; + for (int i = 0; i < connections.length; i++) { + connections[i] = getRandomConnection(c); + } + nodes[r][c].initialise(resources.getRandomFunction(), connections); + } + } + + for (Output output : outputs) { + output.setConnection(0, getRandomConnection()); + } + + } + + /** + * @param clone + */ + public void copyConnections(Chromosome clone) { + int arity = resources.getInt("arity"); + + // copy nodes - [rows][columns] + for (int r = 0; r < nodes.length; r++) { + for (int c = 0; c < nodes[r].length; c++) { + // make array of connections to initialise with + Connection[] connections = new Connection[arity]; + // populate with connections equivalent to clone + Connection copyConnection; + for (int i = 0; i < connections.length; i++) { + copyConnection = clone.getNode(r, c).getConnection(i); + if (copyConnection instanceof Input) { + connections[i] = inputs[((Input) copyConnection).getIndex()]; + } else if (copyConnection instanceof Node) { + connections[i] = nodes[((Node) copyConnection).getRow()][((Node) copyConnection).getColumn()]; + } else { + System.out.println("Warning: Connection of subtype " + copyConnection.getClass().toString() + " is not explicitly handled by copy constructor."); + } + } + // initialise with copied arguments + nodes[r][c].initialise(clone.getNode(r, c).getFunction(), connections); + } + } + + // do the same to outputs + Connection copyOutput; + for (int o = 0; o < outputs.length; o++) { + copyOutput = clone.getOutput(o).getSource(); + if (copyOutput instanceof Input) { + outputs[o].setConnection(0, inputs[((Input) copyOutput).getIndex()]); + } else if (copyOutput instanceof Node) { + outputs[o].setConnection(0, nodes[((Node) copyOutput).getRow()][((Node) copyOutput).getColumn()]); + } else { + // something bad happened + System.out.println("Warning: Connection of subtype " + copyOutput.getClass().toString() + " is not explicitly handled by copy constructor."); + } + } + } + + public Node getNode(int row, int column) { + return nodes[row][column]; + } + + public Output getOutput(int index) { + return outputs[index]; + } + + public Input getInput(int index) { + return inputs[index]; + } + + public int getFitness() { + return fitness; + } + + public void setFitness(int newFitness) { + fitness = newFitness; + } + + /** + * + * @param values + * @throws ParameterMismatchException + */ + public void setInputs(Object ... values) throws ParameterMismatchException { + // if the values provided don't match the specified number of inputs, the user should be warned + if (values.length == inputs.length) { + // set inputs for evaluation + for (int i = 0; i < values.length; i++) { + inputs[i].setValue(values[i]); + } + } else { + throw new ParameterMismatchException(); + } + } + + /** + * This method is useful for mutating chromosomes. + * + * @return a random element that can be mutated - Node or Output + */ + public MutableElement getRandomMutableElement() { + // choose output or node + int index = resources.getRandomInt(outputs.length + (resources.getInt("rows")) * (resources.getInt("columns"))); + + if (index < outputs.length) { + // outputs + return outputs[index]; + } else { + // node + index -= outputs.length; + return nodes[index / (resources.getInt("columns"))][index % (resources.getInt("columns"))]; + } + } + + /** + * Returns a random allowed connection respecting levels back.
+ * This method may always pick inputs, as they can be picked + * regardless of the column. + * + * @param column the column to use as reference + * @return a random connection + */ + public Connection getRandomConnection(int column) { + // work out the allowed range obeying levels back + int allowedColumns = ((column >= (resources.getInt("levelsBack"))) ? (resources.getInt("levelsBack")) : column); + int offset = ((column - allowedColumns) * nodes.length) - inputs.length; + + // choose input or allowed node + int index = resources.getRandomInt(inputs.length + (nodes.length * allowedColumns)); + if (index < inputs.length) { + // input + return inputs[index]; + } else { + // node + // offset it to address the right columns + index += offset; + return nodes[index % nodes.length][index / nodes.length]; + } + } + + /** + * This method will pick a completely random connection, independently + * of levels back, including inputs. + * + * Useful for setting outputs. + * + * + * @return a random connection + */ + public Connection getRandomConnection() { + // choose output or node + int index = resources.getRandomInt(inputs.length + (resources.getInt("columns")) * (resources.getInt("rows"))); + if (index < inputs.length) { + // outputs + return inputs[index]; + } else { + // node + index -= inputs.length; + return nodes[index / (resources.getInt("columns"))][index % (resources.getInt("columns"))]; + } + } + + /** + * This causes the list of active nodes to be recomputed lazily (once it is actually requested). + */ + public void recomputeActiveNodes() { + recomputeActiveNodes = true; + } + + /** + * This method computes a list of active connections (if necessary) and returns it. + * + * @return + */ + public ArrayList getActiveNodes() { + computeActiveNodes(); + return activeNodes; + } + + private void computeActiveNodes() { + // lazy recomputation has been triggered, do it + if (recomputeActiveNodes) { + recomputeActiveNodes = false; + activeNodes = new ArrayList(); + + for (Output output : outputs) { + output.getActiveNodes(activeNodes); + } + } + + } + + public boolean compareTo(Chromosome chromosome) { + for (int r = 0; r < (resources.getInt("rows")); r++) { + for (int c = 0; c < (resources.getInt("columns")); c++) { + if (!(nodes[r][c].copyOf(chromosome.getNode(r, c)))) { + return false; + } + } + } + + for (int o = 0; o < (resources.getInt("outputs")); o++) { + if (!(outputs[o].copyOf(chromosome.getOutput(o)))) { + return false; + } + } + + return true; + } + + public boolean compareActiveTo(Chromosome chromosome) { + // update list if it is out of date + computeActiveNodes(); + + if (activeNodes.size() == chromosome.getActiveNodes().size()) { + for (int i = 0; i < activeNodes.size(); i++) { + if (!(activeNodes.get(i).copyOf(chromosome.getActiveNodes().get(i)))){ + return false; + } + } + return true; + } + return false; + } + + public void printNodes() { + int arity = resources.getInt("arity"); + + for (int r = 0; r < (resources.getInt("rows")); r++) { + System.out.print("r: " + r + "\t"); + for (int c = 0; c < (resources.getInt("columns")); c++) { + System.out.print("N: (" + r + ", " + c + ") "); + for (int i = 0; i < arity; i++) { + System.out.print("C" + i + ": (" + nodes[r][c].getConnection(i).getDescription() + ") "); + } + System.out.print("F: " + nodes[r][c].getFunction().getName() + "\t"); + } + System.out.print("\n"); + } + + for (int o = 0; o < (resources.getInt("outputs")); o++) { + System.out.print("o: " + o + " (" + outputs[o].getSource().getDescription() + ")\t"); + } + } + + public Resources getParameters() { + return resources; + } +} diff --git a/src/jcgp/backend/population/Connection.java b/src/jcgp/backend/population/Connection.java new file mode 100644 index 0000000..ea4f10f --- /dev/null +++ b/src/jcgp/backend/population/Connection.java @@ -0,0 +1,9 @@ +package jcgp.backend.population; + +public interface Connection { + + public Object getValue(); + + public String getDescription(); + +} diff --git a/src/jcgp/backend/population/Gene.java b/src/jcgp/backend/population/Gene.java new file mode 100644 index 0000000..6b90949 --- /dev/null +++ b/src/jcgp/backend/population/Gene.java @@ -0,0 +1,5 @@ +package jcgp.backend.population; + +public abstract class Gene { + +} diff --git a/src/jcgp/backend/population/Input.java b/src/jcgp/backend/population/Input.java new file mode 100644 index 0000000..83fba07 --- /dev/null +++ b/src/jcgp/backend/population/Input.java @@ -0,0 +1,29 @@ +package jcgp.backend.population; + +public class Input extends Gene implements Connection { + + private Object value = 0; + private int index; + + public Input(int index) { + this.index = index; + } + + public void setValue(Object newValue) { + value = newValue; + } + + @Override + public Object getValue() { + return value; + } + + public int getIndex() { + return index; + } + + @Override + public String getDescription() { + return "i: " + index; + } +} diff --git a/src/jcgp/backend/population/MutableElement.java b/src/jcgp/backend/population/MutableElement.java new file mode 100644 index 0000000..8548e63 --- /dev/null +++ b/src/jcgp/backend/population/MutableElement.java @@ -0,0 +1,34 @@ +package jcgp.backend.population; + +public interface MutableElement { + + /** + * This method sets the indexed connection to the specified new connection. + * Implementing classes may choose to ignore the given index (such as in the + * case of outputs, which only have one connection). + * + * @param index + * @param newConnection + */ + public void setConnection(int index, Connection newConnection); + + /** + * This method returns true if and only if:
+ * - the elements being compared are not the same instance;
+ * - the connections of the compared elements are not the same instance;
+ * - the elements have the same function (in the case of Node);
+ * - the grid position of the elements themselves are the same;
+ * - the grid position of all equivalent connections are the same;

+ * + * The relationship computed by this method is:
+ * - symmetric: a.copyOf(b) == b.copyOf(a);
+ * - not reflexive: a.copyOf(a) returns false;
+ * - not transitive: if a.copyOf(b) is true and b.copyOf(c) is true, a.copyOf(c) is + * not necessarily true since it is possible that a == c.
+ * + * @param m + * @return + */ + boolean copyOf(MutableElement m); + +} diff --git a/src/jcgp/backend/population/Node.java b/src/jcgp/backend/population/Node.java new file mode 100644 index 0000000..f1d37a1 --- /dev/null +++ b/src/jcgp/backend/population/Node.java @@ -0,0 +1,112 @@ +package jcgp.backend.population; + +import java.util.ArrayList; +import java.util.Arrays; + +import jcgp.backend.exceptions.InsufficientConnectionsException; +import jcgp.backend.function.Function; + + +public class Node extends Gene implements MutableElement, Connection { + + private Function function; + private Connection[] connections; + private int column, row; + private Chromosome chromosome; + + public Node(Chromosome chromosome, int row, int column, int arity) { + this.chromosome = chromosome; + this.column = column; + this.row = row; + } + + @Override + public Object getValue() { + return function.run(Arrays.copyOfRange(connections, 0, function.getArity())); + } + + public void setFunction(Function newFunction) { + function = newFunction; + } + + @Override + public void setConnection(int index, Connection newConnection) { + connections[index] = newConnection; + chromosome.recomputeActiveNodes(); + } + + public void initialise(Function newFunction, Connection ... newConnections) throws InsufficientConnectionsException { + function = newFunction; + if (newConnections.length == function.getArity()) { + connections = newConnections; + } else { + throw new InsufficientConnectionsException(); + } + } + + public int getColumn() { + return column; + } + + public int getRow() { + return row; + } + + public Function getFunction() { + return function; + } + + public Connection getConnection(int index) { + return connections[index]; + } + + public void getActive(ArrayList activeNodes) { + if (!activeNodes.contains(this)) { + activeNodes.add(this); + } + for (int i = 0; i < function.getArity(); i++) { + if (connections[i] instanceof Node) { + ((Node) connections[i]).getActive(activeNodes); + } + + } + } + + @Override + public boolean copyOf(MutableElement m) { + if (this != m) { + if (m instanceof Node) { + Node n = (Node) m; + if (function == n.getFunction()) { + if (column == n.getColumn() && row == n.getRow()) { + for (int i = 0; i < connections.length; i++) { + if (connections[i] != n.getConnection(i)) { + if (connections[i] instanceof Input && n.getConnection(i) instanceof Input) { + if (((Input) connections[i]).getIndex() != ((Input) n.getConnection(i)).getIndex()) { + return false; + } + } else if (connections[i] instanceof Node && n.getConnection(i) instanceof Node) { + if (((Node) connections[i]).getRow() != ((Node) n.getConnection(i)).getRow() && + ((Node) connections[i]).getColumn() != ((Node) n.getConnection(i)).getColumn()) { + return false; + } + } else { + return false; + } + } else { + return false; + } + } + return true; + } + } + } + } + return false; + } + + @Override + public String getDescription() { + return "n: " + row + ", " + column; + } +} diff --git a/src/jcgp/backend/population/Output.java b/src/jcgp/backend/population/Output.java new file mode 100644 index 0000000..aa94ab6 --- /dev/null +++ b/src/jcgp/backend/population/Output.java @@ -0,0 +1,69 @@ +package jcgp.backend.population; + +import java.util.ArrayList; + +public class Output extends Gene implements MutableElement { + + private Connection source; + private Chromosome chromosome; + private int index; + + public Output(Chromosome chromosome, int index) { + this.chromosome = chromosome; + this.index = index; + //this.source = new SimpleObjectProperty(); + } + + public Object calculate() { + Object result = source.getValue(); + return result; + } + + @Override + public void setConnection(int index, Connection newConnection) { + source = newConnection; + chromosome.recomputeActiveNodes(); + } + + public int getIndex() { + return index; + } + + public Connection getSource() { + return source; + } + +// public SimpleObjectProperty sourceProperty() { +// return source; +// } + + public void getActiveNodes(ArrayList activeNodes) { + if (source instanceof Node) { + ((Node) source).getActive(activeNodes); + } + } + + @Override + public boolean copyOf(MutableElement m) { + if (this != m) { + if (m instanceof Output) { + Output o = (Output) m; + if (index == o.getIndex()) { + if (source != o.getSource()) { + if (source instanceof Input && o.getSource() instanceof Input) { + if (((Input) source).getIndex() == ((Input) o.getSource()).getIndex()) { + return true; + } + } else if (source instanceof Node && o.getSource() instanceof Node) { + if (((Node) source).getRow() == ((Node) o.getSource()).getRow() && + ((Node) source).getColumn() == ((Node) o.getSource()).getColumn()) { + return true; + } + } + } + } + } + } + return false; + } +} diff --git a/src/jcgp/backend/population/Population.java b/src/jcgp/backend/population/Population.java new file mode 100644 index 0000000..7049d79 --- /dev/null +++ b/src/jcgp/backend/population/Population.java @@ -0,0 +1,45 @@ +package jcgp.backend.population; + +import jcgp.JCGP.Resources; + + +public class Population { + + private Chromosome[] chromosomes; + private int fittest; + + public Population(Resources resources) { + chromosomes = new Chromosome[(resources.getInt("popSize"))]; + for (int c = 0; c < chromosomes.length; c++) { + chromosomes[c] = new Chromosome(resources); + } + } + + public Population(Chromosome parent, Resources resources) { + chromosomes = new Chromosome[(resources.getInt("popSize"))]; + // make a clone for safety + this.chromosomes[0] = new Chromosome(parent); + // generate the rest of the individuals + for (int c = 1; c < chromosomes.length; c++) { + chromosomes[c] = new Chromosome(chromosomes[0]); + } + } + + /** + * Returns all chromosomes, parents first, then offspring. + * + * @param index + * @return + */ + public Chromosome getChromosome(int index) { + return chromosomes[index]; + } + + public void setBestIndividual(int index) { + fittest = index; + } + + public Chromosome getBestIndividual() { + return chromosomes[fittest]; + } +} -- cgit v1.2.3