diff options
author | Eduardo Pedroni <ep625@york.ac.uk> | 2014-02-13 22:41:26 +0000 |
---|---|---|
committer | Eduardo Pedroni <ep625@york.ac.uk> | 2014-02-13 22:41:26 +0000 |
commit | 6419b69faeb4736db1ccb50cfa5a030f9aa818aa (patch) | |
tree | ca424a5ea85abf044cd4b22c3c43608163ae5f74 | |
parent | 3326c58f4d2d7e8c77738277dcd093aa864ad2a5 (diff) |
Added methods in Chromosome to compare active and all nodes. Associated tests also written.
-rw-r--r-- | README | 10 | ||||
-rw-r--r-- | src/jcgp/population/Chromosome.java | 63 | ||||
-rw-r--r-- | src/jcgp/population/Connection.java | 4 | ||||
-rw-r--r-- | src/jcgp/population/Input.java | 9 | ||||
-rw-r--r-- | src/jcgp/population/MutableElement.java | 18 | ||||
-rw-r--r-- | src/jcgp/population/Node.java | 60 | ||||
-rw-r--r-- | src/jcgp/population/Output.java | 30 | ||||
-rw-r--r-- | src/jcgp/tests/ChromosomeTests.java | 113 | ||||
-rw-r--r-- | src/jcgp/tests/NodeTests.java | 28 | ||||
-rw-r--r-- | src/jcgp/tests/OutputTests.java | 11 |
10 files changed, 236 insertions, 110 deletions
@@ -120,6 +120,14 @@ Population tests under way. 13/2 Writing the test for the population copy constructor will require the production of a method which compares two chromosomes. -It might be useful to incorporate this into the program as it is an interesting utility function. + +Therefore, two new methods will be written for Chromosome: compareTo and compareActiveTo. Compare returns true if the two chromosomes +are exclusively equivalent, and compareActive returns true if the active nodes within the two chromosomes are exclusively equivalent. + +Compare methods done, including their "dependency": copyOf method in MutableElement. This method is akin to equals(), with the exception that +it returns false when the compared objects are pointers to a common instance. + +Minor update: Node now passes only the necessary number of arguments into its function; this allows the node to compute its active +connections as it knows which connections the function will use (based on arity). diff --git a/src/jcgp/population/Chromosome.java b/src/jcgp/population/Chromosome.java index 08ff9b9..f61b780 100644 --- a/src/jcgp/population/Chromosome.java +++ b/src/jcgp/population/Chromosome.java @@ -11,8 +11,8 @@ public class Chromosome { private Input[] inputs; private Node[][] nodes; private Output[] outputs; - - private ArrayList<Connection> activeNodes; + + private ArrayList<Node> activeNodes; private int fitness = 0; private boolean recomputeActiveNodes = true; @@ -117,7 +117,7 @@ public class Chromosome { 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++) { @@ -153,6 +153,11 @@ public class Chromosome { fitness = newFitness; } + /** + * + * @param values + * @throws ParameterMismatchException + */ public void setInputs(int ... 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) { @@ -232,31 +237,67 @@ public class Chromosome { return nodes[index / Parameters.getColumns()][index % Parameters.getColumns()]; } } - + /** * 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 nodes (if necessary) and returns it. + * This method computes a list of active connections (if necessary) and returns it. * * @return */ - public ArrayList<Connection> getActiveNodes() { + public ArrayList<Node> getActiveNodes() { + computeActiveNodes(); + return activeNodes; + } + + private void computeActiveNodes() { // lazy recomputation has been triggered, do it if (recomputeActiveNodes) { recomputeActiveNodes = false; - activeNodes = new ArrayList<Connection>(); - + activeNodes = new ArrayList<Node>(); + for (Output output : outputs) { output.getActiveNodes(activeNodes); } - } - return activeNodes; + } + public boolean compareTo(Chromosome chromosome) { + for (int r = 0; r < Parameters.getRows(); r++) { + for (int c = 0; c < Parameters.getColumns(); c++) { + if (!(nodes[r][c].copyOf(chromosome.getNode(r, c)))) { + return false; + } + } + } + + for (int o = 0; o < Parameters.getOutputs(); 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; + } } diff --git a/src/jcgp/population/Connection.java b/src/jcgp/population/Connection.java index 12e92d6..3312de3 100644 --- a/src/jcgp/population/Connection.java +++ b/src/jcgp/population/Connection.java @@ -1,11 +1,7 @@ package jcgp.population; -import java.util.ArrayList; - public interface Connection { public int getValue(); - public void getActive(ArrayList<Connection> activeNodes); - } diff --git a/src/jcgp/population/Input.java b/src/jcgp/population/Input.java index f3199b8..ee008ce 100644 --- a/src/jcgp/population/Input.java +++ b/src/jcgp/population/Input.java @@ -1,7 +1,5 @@ package jcgp.population; -import java.util.ArrayList; - public class Input implements Connection { private int value = 0, index; @@ -23,11 +21,4 @@ public class Input implements Connection { return index; } - @Override - public void getActive(ArrayList<Connection> activeNodes) { - if (!activeNodes.contains(this)) { - activeNodes.add(this); - } - } - } diff --git a/src/jcgp/population/MutableElement.java b/src/jcgp/population/MutableElement.java index 5eae4ef..114a4ab 100644 --- a/src/jcgp/population/MutableElement.java +++ b/src/jcgp/population/MutableElement.java @@ -3,5 +3,23 @@ package jcgp.population; public interface MutableElement { public void setConnection(Connection newConnection); + + /** + * This method returns true if and only if:</br> + * - the elements being compared are not the same instance;</br> + * - the connections of the compared elements are not the same instance;</br> + * - the elements have the same function (in the case of Node);</br> + * - the grid position of the elements themselves are the same;</br> + * - the grid position of all equivalent connections are the same;</br></br> + * + * The relationship computed by this method is:</br> + * - symmetric: a.copyOf(b) == b.copyOf(a);</br> + * - not reflexive: a.copyOf(a) returns false;</br> + * - not transitive: if a.copyOf(b) is true and b.copyOf(c) is true, a.copyOf(c) is not necessarily true;</br> + * + * @param m + * @return + */ + boolean copyOf(MutableElement m); } diff --git a/src/jcgp/population/Node.java b/src/jcgp/population/Node.java index c09532c..b2e29df 100644 --- a/src/jcgp/population/Node.java +++ b/src/jcgp/population/Node.java @@ -1,6 +1,7 @@ package jcgp.population; import java.util.ArrayList; +import java.util.Arrays; import jcgp.Parameters; import jcgp.Utilities; @@ -8,7 +9,7 @@ import jcgp.function.Function; public class Node implements MutableElement, Connection { - + private Function function; private Connection[] connections; private int column, row; @@ -18,28 +19,29 @@ public class Node implements MutableElement, Connection { this.chromosome = chromosome; this.column = column; this.row = row; + this.connections = new Connection[Parameters.getMaxArity()]; } - + @Override public int getValue() { - return function.run(connections); + return function.run(Arrays.copyOfRange(connections, 0, function.getArity())); } - + public void setFunction(Function newFunction) { function = newFunction; chromosome.recomputeActiveNodes(); } - + @Override public void setConnection(Connection newConnection) { connections[Utilities.getRandomInt(connections.length)] = newConnection; chromosome.recomputeActiveNodes(); } - + public void initialise(Function newFunction, Connection ... newConnections) throws InsufficientConnectionsException { - + function = newFunction; - + if (newConnections.length >= Parameters.getMaxArity()) { connections = newConnections; } else { @@ -48,7 +50,6 @@ public class Node implements MutableElement, Connection { } public int getColumn() { - return column; } @@ -64,13 +65,48 @@ public class Node implements MutableElement, Connection { return connections[index]; } - @Override - public void getActive(ArrayList<Connection> activeNodes) { + public void getActive(ArrayList<Node> activeNodes) { if (!activeNodes.contains(this)) { activeNodes.add(this); } for (int i = 0; i < function.getArity(); i++) { - connections[i].getActive(activeNodes); + 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; } } diff --git a/src/jcgp/population/Output.java b/src/jcgp/population/Output.java index 0171d7b..f0bcbbf 100644 --- a/src/jcgp/population/Output.java +++ b/src/jcgp/population/Output.java @@ -31,7 +31,33 @@ public class Output implements MutableElement { return source; } - public void getActiveNodes(ArrayList<Connection> activeNodes) { - source.getActive(activeNodes); + public void getActiveNodes(ArrayList<Node> 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/tests/ChromosomeTests.java b/src/jcgp/tests/ChromosomeTests.java index ff1de18..d2407a6 100644 --- a/src/jcgp/tests/ChromosomeTests.java +++ b/src/jcgp/tests/ChromosomeTests.java @@ -36,6 +36,9 @@ import org.junit.Test; * - It should feature a clone constructor, which creates a deep copy of a * specified Chromosome object. * - It should be able to return a list of active nodes. + * - It should contain a method to evaluate whether a given chromosome is identical + * to it. + * - Same as above, but only looking at the active portion of a chromosome. * * TODO: bashing (strange value ranges, etc) * @@ -82,29 +85,21 @@ public class ChromosomeTests { // create a clone, check to see if it really is a clone Chromosome clone = new Chromosome(chromosome); - // set input values - testInputs = new int[Parameters.getInputs()]; - for (int i = 0; i < Parameters.getInputs(); i++) { - testInputs[i] = i * 2 - 3; - } - chromosome.setInputs(testInputs); - clone.setInputs(testInputs); - // compare all elements, one by one // check outputs for (int o = 0; o < Parameters.getOutputs(); o++) { // check that no cross-references exist between chromosomes - assertTrue("Cloned chromosome contained a reference to a member of the original chromosome.", - c.getOutput(o) != oc.getOutput(o) && - c.getOutput(o).getSource() != oc.getOutput(o).getSource()); + assertTrue("Cloned chromosome contains a reference to a member of the original chromosome.", + clone.getOutput(o) != chromosome.getOutput(o) && + clone.getOutput(o).getSource() != chromosome.getOutput(o).getSource()); // check that the connections are equivalent - if (c.getOutput(o).getSource() instanceof Input && oc.getOutput(o).getSource() instanceof Input) { + if (clone.getOutput(o).getSource() instanceof Input && chromosome.getOutput(o).getSource() instanceof Input) { assertTrue("Outputs did not connect to equivalent inputs.", - ((Input) c.getOutput(o).getSource()).getIndex() == ((Input) oc.getOutput(o).getSource()).getIndex()); - } else if (c.getOutput(o).getSource() instanceof Node && oc.getOutput(o).getSource() instanceof Node) { + ((Input) clone.getOutput(o).getSource()).getIndex() == ((Input) chromosome.getOutput(o).getSource()).getIndex()); + } else if (clone.getOutput(o).getSource() instanceof Node && chromosome.getOutput(o).getSource() instanceof Node) { assertTrue("Outputs did not connect to equivalent nodes.", - ((Node) c.getOutput(o).getSource()).getRow() == ((Node) oc.getOutput(o).getSource()).getRow() && - ((Node) c.getOutput(o).getSource()).getColumn() == ((Node) oc.getOutput(o).getSource()).getColumn()); + ((Node) clone.getOutput(o).getSource()).getRow() == ((Node) chromosome.getOutput(o).getSource()).getRow() && + ((Node) clone.getOutput(o).getSource()).getColumn() == ((Node) chromosome.getOutput(o).getSource()).getColumn()); } else { fail("Output source types did not match."); } @@ -112,24 +107,37 @@ public class ChromosomeTests { // check nodes, rows first for (int row = 0; row < Parameters.getRows(); row++) { for (int column = 0; column < Parameters.getColumns(); column++) { - // look at each connection + // check that nodes are not pointers to the same instance + assertTrue("Both chromosomes contain a reference to the same node.", clone.getNode(row, column) != chromosome.getNode(row, column)); + // check that both nodes reference their own position in the grid correctly + assertTrue("Equivalent nodes self-reference differently.", clone.getNode(row, column).getRow() == chromosome.getNode(row, column).getRow() && + clone.getNode(row, column).getColumn() == chromosome.getNode(row, column).getColumn()); + // check that the two nodes have the same function + assertTrue("Equivalent nodes have different functions.", clone.getNode(row, column).getFunction() == chromosome.getNode(row, column).getFunction()); + + // compare each connection for (int connection = 0; connection < Parameters.getMaxArity(); connection++) { - if (c.getNode(row, column).getConnection(connection) instanceof Input && - oc.getNode(row, column).getConnection(connection) instanceof Input) { + // first look at whether they are actually the same instance + assertTrue("Nodes are connected to the same connection instance.", + clone.getNode(row, column).getConnection(connection) != chromosome.getNode(row, column).getConnection(connection)); + + // if the connections aren't the same instance, check that their addresses are the same + if (clone.getNode(row, column).getConnection(connection) instanceof Input && + chromosome.getNode(row, column).getConnection(connection) instanceof Input) { assertTrue("Nodes did not connect to equivalent inputs.", - ((Input) c.getNode(row, column).getConnection(connection)).getIndex() == - ((Input) oc.getNode(row, column).getConnection(connection)).getIndex()); + ((Input) clone.getNode(row, column).getConnection(connection)).getIndex() == + ((Input) chromosome.getNode(row, column).getConnection(connection)).getIndex()); - } else if (c.getNode(row, column).getConnection(connection) instanceof Node && - oc.getNode(row, column).getConnection(connection) instanceof Node) { + } else if (clone.getNode(row, column).getConnection(connection) instanceof Node && + chromosome.getNode(row, column).getConnection(connection) instanceof Node) { assertTrue("Nodes did not connect to equivalent nodes.", - ((Node) c.getNode(row, column).getConnection(connection)).getRow() == - ((Node) oc.getNode(row, column).getConnection(connection)).getRow() && + ((Node) clone.getNode(row, column).getConnection(connection)).getRow() == + ((Node) chromosome.getNode(row, column).getConnection(connection)).getRow() && - ((Node) c.getNode(row, column).getConnection(connection)).getColumn() == - ((Node) oc.getNode(row, column).getConnection(connection)).getColumn()); + ((Node) clone.getNode(row, column).getConnection(connection)).getColumn() == + ((Node) chromosome.getNode(row, column).getConnection(connection)).getColumn()); } else { fail("Connection types did not match."); @@ -138,17 +146,27 @@ public class ChromosomeTests { } } - - // change clone inputs, outputs should no longer match + // set input values testInputs = new int[Parameters.getInputs()]; for (int i = 0; i < Parameters.getInputs(); i++) { - testInputs[i] = i * 2; + testInputs[i] = (i + 1) * 2 - 3; } + chromosome.setInputs(testInputs); clone.setInputs(testInputs); + + // check that both chromosomes have the same outputs for (int i = 0; i < Parameters.getOutputs(); i++) { - assertTrue("Incorrect output returned.", chromosome.getOutput(i).calculate() != + assertTrue("Incorrect output returned.", chromosome.getOutput(i).calculate() == clone.getOutput(i).calculate()); } + + // mutate an active node in clone, check that the same node in chromosome produces a different output + // NOTE: given a small grid this mutation may actually pick the same connection (causing no effective change) and therefore the test would fail. + Node node = clone.getActiveNodes().get(Utilities.getRandomInt(clone.getActiveNodes().size())); + node.setConnection(clone.getRandomConnection(node.getColumn())); + + assertTrue("Mutation affected both nodes in both chromosomes.", node.getValue() != chromosome.getNode(node.getRow(), node.getColumn()).getValue()); + } /** @@ -282,12 +300,41 @@ public class ChromosomeTests { chromosome.getNode(0, 0).setConnection(chromosome.getInput(0)); - assertTrue("Active connection not in list.", chromosome.getActiveNodes().contains(chromosome.getInput(0))); - assertTrue("Active connection not in list.", chromosome.getActiveNodes().contains(chromosome.getNode(0, 0))); + assertTrue("Active node not in list.", chromosome.getActiveNodes().contains(chromosome.getNode(0, 0))); // change outputs, print list chromosome.getOutput(0).setConnection(chromosome.getNode(0, Parameters.getColumns() - 1)); System.out.println("Active connections: " + chromosome.getActiveNodes().toString() + "\n"); } + + /** + * + */ + @Test + public void compareActiveTest() { + // create a clone of the chromosome, compare active nodes - should return true + Chromosome c = new Chromosome(chromosome); + assertTrue("Active nodes did not match.", chromosome.compareActiveTo(c)); + assertTrue("Symmetry not obeyed.", c.compareActiveTo(chromosome)); + + // create a new random chromosome, this time they should not match + c = new Chromosome(); + assertTrue("Active nodes did match.", !chromosome.compareActiveTo(c)); + } + + /** + * + */ + @Test + public void compareTest() { + // create a clone of the chromosome, compare - should return true + Chromosome c = new Chromosome(chromosome); + assertTrue("Chromosomes did not match.", chromosome.compareTo(c)); + assertTrue("Symmetry not obeyed.", c.compareTo(chromosome)); + + // create a new random chromosome, this time they should not match + c = new Chromosome(); + assertTrue("Chromosomes did match.", !chromosome.compareTo(c)); + } } diff --git a/src/jcgp/tests/NodeTests.java b/src/jcgp/tests/NodeTests.java index ecc87ff..5b378d2 100644 --- a/src/jcgp/tests/NodeTests.java +++ b/src/jcgp/tests/NodeTests.java @@ -2,7 +2,6 @@ package jcgp.tests; import static org.junit.Assert.assertTrue; -import java.util.ArrayList; import java.util.Random; import jcgp.Parameters; @@ -92,11 +91,6 @@ public class NodeTests { return arg1; } - @Override - public void getActive(ArrayList<Connection> activeNodes) { - // irrelevant for this test - } - }, new Connection() { @@ -106,11 +100,6 @@ public class NodeTests { return arg2; } - @Override - public void getActive(ArrayList<Connection> activeNodes) { - // irrelevant for this test - } - }}); } @@ -189,11 +178,6 @@ public class NodeTests { return 0; } - @Override - public void getActive(ArrayList<Connection> activeNodes) { - // blank - - } }; conn1 = new Connection() { @@ -203,11 +187,6 @@ public class NodeTests { return 0; } - @Override - public void getActive(ArrayList<Connection> activeNodes) { - // blank - - } }; node.initialise(null, conn0, conn1); @@ -222,17 +201,12 @@ public class NodeTests { // blank return 0; } - - @Override - public void getActive(ArrayList<Connection> activeNodes) { - // blank - - } }; node.setConnection(conn2); assertTrue("Connection was not found in node.", node.getConnection(0) == conn2 || node.getConnection(1) == conn2); } + } diff --git a/src/jcgp/tests/OutputTests.java b/src/jcgp/tests/OutputTests.java index 06295ae..04eac7f 100644 --- a/src/jcgp/tests/OutputTests.java +++ b/src/jcgp/tests/OutputTests.java @@ -2,7 +2,6 @@ package jcgp.tests; import static org.junit.Assert.assertTrue; -import java.util.ArrayList; import java.util.Random; import jcgp.Parameters; @@ -69,11 +68,6 @@ public class OutputTests { // test value return outputValue; } - - @Override - public void getActive(ArrayList<Connection> activeNodes) { - // blank - } }); assertTrue("Incorrect evaluation.", output.calculate() == outputValue); @@ -89,11 +83,6 @@ public class OutputTests { // blank return 0; } - - @Override - public void getActive(ArrayList<Connection> activeNodes) { - // blank - } }; output.setConnection(newConn); |