package jcgp.backend.population; import java.util.ArrayList; import jcgp.backend.function.Function; import jcgp.backend.resources.Resources; /** * Nodes make up the main part of the chromosome, * where the actual functions are evolved. Each node * contains a function and a number of connections. * The node outputs the result of performing its function * on the values of its connections. Nodes therefore * implement both {@code Mutable} and {@code Connection} * since they can be mutated but also connected to. * Nodes are constructed with a fixed number of connections * (determined by the maximum arity of the function set) * and must be reinstantiated if the experiment arity * changes. *

* When mutating a node, it is easiest to use {@code mutate()}. * Alternatively, you may also perform a specific mutation using * {@code setConnection(...)} and {@code setFunction(...)}. * * @author Eduardo Pedroni * */ public class Node implements Mutable, Connection { private Function function; private Connection[] connections; private int column, row; private Chromosome chromosome; /** * Constructs a new instance of {@code Node} with the * specified parameters. Nodes must contain their * own row and column for ease of copying. * * @param chromosome the chromosome this node belongs to. * @param row the node's row. * @param column the node's column. */ public Node(Chromosome chromosome, int row, int column) { this.chromosome = chromosome; this.column = column; this.row = row; } /** * Initialises the node with the specified values. * The number of connections passed as argument must * be exactly the same as the experiment arity, or * an {@code IllegalArgumentException} will be thrown. * * @param newFunction the node function to set. * @param newConnections the node connections to set. */ public void initialise(Function newFunction, Connection... newConnections) { function = newFunction; if (newConnections.length == chromosome.getResources().arity()) { connections = newConnections; } else { throw new IllegalArgumentException("Received " + newConnections.length + " connections but needed exactly " + chromosome.getResources().arity()); } } /** * @return this node's column. */ public int getColumn() { return column; } /** * @return this node's row. */ public int getRow() { return row; } /** * @return this node's function. */ public Function getFunction() { return function; } /** * Sets the node function. * * @param newFunction the new function to set. */ public void setFunction(Function newFunction) { function = newFunction; } /** * @param index the connection to return. * @return the indexed connection. */ public Connection getConnection(int index) { return connections[index]; } /** * This method sets the indexed connection to the specified new connection. * If the given connection is null or disrespects levels back, it is discarded * and no connections are changed. * * @param index the connection index to set. * @param newConnection the {@code Connection} to connect to. */ public void setConnection(int index, Connection newConnection) { // connection must not be null if (newConnection != null) { connections[index] = newConnection; chromosome.recomputeActiveNodes(); } } /** * For package use, this is a recursive method * used to create a collection of active nodes * in the chromosome. If this node is not already * in the active node list, this method adds it. * It then calls {@code getActive()} on each of its * connections, therefore recursively adding every * single active node to the given list. * * @param activeNodes the list of active nodes being built. */ protected void getActive(ArrayList activeNodes) { // don't add the same node twice if (!activeNodes.contains(this)) { activeNodes.add(this); } // call getActive on all connections - they are all active for (int i = 0; i < function.getArity(); i++) { if (connections[i] instanceof Node) { ((Node) connections[i]).getActive(activeNodes); } } } @Override public boolean copyOf(Mutable element) { // both cannot be the same instance if (this != element) { // element must be instance of node if (element instanceof Node) { Node n = (Node) element; // must have the same function if (function == n.getFunction()) { // row and column must be the same if (column == n.getColumn() && row == n.getRow()) { // connections must be the equivalent, but not the same instance 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; } } // all connections checked, this really is a copy return true; } } } } return false; } @Override public Object getValue() { // build list of arguments recursively Object[] args = new Object[function.getArity()]; for (int i = 0; i < function.getArity(); i++) { args[i] = connections[i].getValue(); } // return function result return function.run(args); } @Override public void mutate() { Resources resources = chromosome.getResources(); // choose to mutate the function or a connection int geneType = resources.getRandomInt(1 + resources.arity()); // if the int is less than 1, mutate function, else mutate connections if (geneType < 1) { setFunction(resources.getRandomFunction()); } else { // if we decided to mutate connection, subtract 1 from geneType so it fits into the arity range geneType--; setConnection(geneType, chromosome.getRandomConnection(column)); } } @Override public String toString() { return "Node [" + row + ", " + column + "]"; } }