/** * @author Steven L. Moxley * @version 1.2 */ package org.futurist.neuralnet; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashSet; import java.util.InputMismatchException; import java.util.Random; import java.util.Stack; import java.util.concurrent.ConcurrentLinkedQueue; import javafx.scene.layout.GridPane; import org.futurist.neuralnet.node.DirectedNode; import org.futurist.neuralnet.node.DirectedSumNode; import org.futurist.neuralnet.node.Node; import org.futurist.neuralnet.node.UndirectedNode; import org.apache.commons.math3.distribution.*; public class Network extends Thread { public static final Integer DEFAULT_LENGTH = 100; public static final Integer DEFAULT_WIDTH = 100; public static final Integer DEFAULT_HEIGHT = 100; public static final Integer DEFAULT_SEARCH_DIST = 1000; protected final Integer id; protected final Random rng; // random node selector protected final AbstractRealDistribution dist; // probability distribution that determines which nodes (neurons) to connect with an edge (synapse) protected Integer startNodes; // number of nodes in the input layer/functional unit protected Integer layerNodes; // number of nodes per hidden layer; UPGRADE: set to -1 for random number for each layer, -2...-x as per differential tests protected Integer endNodes; // number of nodes in the output layer/functional unit protected Integer startEdges; // number of connected synapses in the initial network protected Integer layers; // number of hidden layers/hidden networks protected ArrayList<Node> nodes; // all nodes in the network protected ArrayList<Edge> edges; // all edges in the network protected ArrayList<Edge[]> functionalUnits; // subset of edges that fire together to form a functional unity; a.k.a. hidden layer of neurons; funtionalUnits[0] is the input layer of neurons, and functionalUnits[functionalUnits.length] is the output layer of neurons protected Number[] idealValues; // correct values that should be output by last layer of functional units; used for learning step protected DirectedNode[] inputNeurons; // the first functional unit receives input that is fed to the rest of the network protected DirectedNode[] outputNeurons; // the last functional unit outputs the values resulting from neuron firing and learning protected Double learnRate; // learning weight; used for re-weighting edges protected Double learnScale; // factor by which learning rate decays linearly w.r.t. distance from output layer protected final int x; // physical length of network; the number of coordinates available to place Nodes along the x-axis protected final int y; // physical width of network; the number of coordinates available to place Nodes along the y-axis protected final int z; // physical height of network; the number of coordinates available to place Nodes along the z-axis protected GridPane pane; // GUI protected boolean simulated; // determines what type of statistics to print protected Double[][] adjacencyMatrix; // synaptic connection weights between neuron nodes; adjacencyMatrix[i][j]=0 iff i=j; adjacencyMatrix[i][j]=w iff an Edge exists from i to j; adjacencyMatrix[i][j]=Double.MAX_VALUE otherwise protected Double[][] shortestPaths; // shortest paths between neuron nodes; protected Integer simRounds; // number of simulation rounds to run protected Boolean simLearning; // whether or not learning through backpropagation is enabled during the simulation rounds /** * Default constructor to create a Network. * @param id the unique ID of this Network. * @param v the correct/ideal/target values that are used to train the network. */ public Network(Integer id, Number[] v) { this.id = id; rng = new Random(); dist = new NormalDistribution(); startNodes = 256; nodes = new ArrayList<Node>(startNodes); startEdges = startNodes * 3; edges = new ArrayList<Edge>(startEdges); layers = 3; functionalUnits = new ArrayList<Edge[]>(layers+2); idealValues = dataToDouble(v); endNodes = idealValues.length; learnRate = .25; x = DEFAULT_LENGTH; y = DEFAULT_WIDTH; z = DEFAULT_HEIGHT; simRounds = 5; simLearning = true; init(); } /** * Constructor to create a Network with the given parameters using the default 3-dimensional space. * @param id the unique ID of this Network. * @param d the statistical distribution drawn from to determine neurons' initial values and firing thresholds. * @param inNodes the number of input neurons. * @param lNodes the number of neurons in each hidden layer. * @param outNodes the number of output neurons. * @param numEdges the number of synapses. * @param l the the number of layers. * @param r the the learning rate used for reinforcement in backpropagation. * @param s the scaling (or dampening) factor used to modulate the learning rate as backpropagation takes place. * @param v the correct/ideal/target values that are used to train the network. */ public Network(Integer id, AbstractRealDistribution d, Integer inNodes, Integer lNodes, Integer outNodes, Integer numEdges, Integer l, Double r, Double s, Number[] v) { if(outNodes != v.length) { throw new InputMismatchException("The number of output neurons and the number of target values must match."); } else { this.id = id; rng = new Random(); dist = d; startNodes = inNodes; layerNodes = lNodes; endNodes = outNodes; nodes = new ArrayList<Node>(startNodes); startEdges = numEdges; edges = new ArrayList<Edge>(startEdges); layers = l; functionalUnits = new ArrayList<Edge[]>(layers+2); idealValues = dataToDouble(v); learnRate = r; learnScale = s; x = DEFAULT_LENGTH; y = DEFAULT_WIDTH; z = DEFAULT_HEIGHT; simRounds = 5; simLearning = true; init(); } } /** * Constructor to create a Network with the given parameters using the given 3-dimensional space. * @param id the unique ID of this Network. * @param d the statistical distribution drawn from to determine neurons' initial values and firing thresholds. * @param inNodes the number of input neurons. * @param lNodes the number of neurons in each hidden layer. * @param outNodes the number of output neurons. * @param numEdges the number of synapses. * @param l the the number of layers. * @param r the the learning rate used for reinforcement in backpropagation. * @param s the scaling (or dampening) factor used to modulate the learning rate as backpropagation takes place. * @param v the correct/ideal/target values that are used to train the network. * @param length the number of available locations in the x dimension (or points along the x-axis) where the Network may place a Node. * @param width the number of available locations in the y dimension (or points along the y-axis) where the Network may place a Node. * @param height the number of available locations in the z dimension (or points along the z-axis) where the Network may place a Node. */ public Network(Integer id, AbstractRealDistribution d, Integer inNodes, Integer lNodes, Integer outNodes, Integer numEdges, Integer l, Double r, Double s, Number[] v, Integer length, Integer width, Integer height) { if(outNodes != v.length) { throw new InputMismatchException("The number of output neurons and the number of target values must match."); } else { this.id = id; rng = new Random(); dist = d; startNodes = inNodes; layerNodes = lNodes; endNodes = outNodes; nodes = new ArrayList<Node>(startNodes); startEdges = numEdges; edges = new ArrayList<Edge>(startEdges); layers = l; functionalUnits = new ArrayList<Edge[]>(layers+2); idealValues = dataToDouble(v); learnRate = r; learnScale = s; x = length; y = width; z = height; simRounds = 5; simLearning = true; init(); } } /** * Constructor to create a Network with the given parameters using the default 3-dimensional. * @param i the unique ID of this Network. * @param d the statistical distribution drawn from to determine neurons' initial values and firing thresholds. * @param inNodes the number of input neurons. * @param lNodes the number of neurons in each hidden layer. * @param outNodes the number of output neurons. * @param numEdges the number of synapses. * @param l the the number of layers. * @param r the the learning rate used for reinforcement in backpropagation. * @param s the scaling (or dampening) factor used to modulate the learning rate as backpropagation takes place. * @param v the correct/ideal/target values that are used to train the network. */ public Network(Integer i, AbstractRealDistribution d, Integer inNodes, Integer lNodes, Integer outNodes, Integer numEdges, Integer l, Double r, Double s, Number[] v, GridPane gui) { this(i, d, inNodes, lNodes, outNodes, numEdges, l, r, s, v); pane = gui; } private void init() { long startTime = System.nanoTime(); // store free remaining <X, Y, Z> coordinates for placing Nodes // UPGRADE: find better than O(N^3) way of doing this ArrayList<ArrayList<Integer>> undrawnIDs = new ArrayList<ArrayList<Integer>>(x*y*z); for(int xIdx = 1; xIdx < x; xIdx++) { for(int yIdx = 1; yIdx < y; yIdx++) { for(int zIdx = 1; zIdx < z; zIdx++) { ArrayList<Integer> ID = new ArrayList<Integer>(); ID.add(x); ID.add(y); ID.add(z); undrawnIDs.add(ID); } } } // create nodes for all hidden layers with values draw from the network's probability distribution for(int i = 0; i < layerNodes*layers; i++) { ArrayList<Integer> coordinates = undrawnIDs.get(rng.nextInt(undrawnIDs.size())); nodes.add(new DirectedSumNode(coordinates, dist.cumulativeProbability(i), dist.density(i), null, null)); undrawnIDs.remove(coordinates); } // create edges for all hidden layers with weights from the network's probability distribution for each functional level for(int i = 0; i < layers; i++) { // make the functional unit (subset of nodes) that are connected at this level Edge[] unit = new Edge[layerNodes]; for(int j = 0; j < layerNodes; j++) { Node n1 = nodes.get(rng.nextInt(nodes.size())); Node n2 = nodes.get(rng.nextInt(nodes.size())); while(n1 == n2) { n2 = nodes.get(rng.nextInt(nodes.size())); } Integer edgeID = j + (i*j); //Edge e = new Edge(edgeID, n1, n2); //Edge e = new Edge(edgeID, n1, n2, dist.density(edgeID)); Edge e = new Edge(edgeID, n1, n2, dist.cumulativeProbability(edgeID)); if(n1 instanceof UndirectedNode && n2 instanceof UndirectedNode) { //System.out.println(n1 + " and " + n2 + " are both UndirectedNodes!"); ((UndirectedNode) n1).addNeighbor(e); ((UndirectedNode) n2).addNeighbor(e); } else if(n1 instanceof DirectedNode && n2 instanceof DirectedNode) { //System.out.println(n1 + " and " + n2 + " are both DirectedNodes!"); ((DirectedNode) n1).addOutput(e); ((DirectedNode) n2).addInput(e); } else { System.out.println("ERROR: " + n1 + " and " + n2 + " are not the same type of node!"); } unit[j] = e; edges.add(e); } functionalUnits.add(unit); //System.out.println("Initialized functional unit " + i + " with " + unit.length + " nodes."); } // create input layer neurons inputNeurons = new DirectedNode[startNodes]; for(int i = 0; i < startNodes; i++) { ArrayList<Integer> coordinates = undrawnIDs.get(rng.nextInt(undrawnIDs.size())); DirectedSumNode inNode = new DirectedSumNode(coordinates, 0.0, 1.0, null, null); inputNeurons[i] = inNode; undrawnIDs.remove(coordinates); } // connect each input neuron to each neuron in the next layer for(DirectedNode inNode : inputNeurons) { for(Edge e : functionalUnits.get(0)) { e.setInput(inNode); inNode.addOutput(e); } } // create output layer neurons outputNeurons = new DirectedNode[endNodes]; for(int i = 0; i < endNodes; i++) { ArrayList<Integer> coordinates = undrawnIDs.get(rng.nextInt(undrawnIDs.size())); DirectedSumNode outNode = new DirectedSumNode(coordinates, 0.0, 1.0, null, null); outputNeurons[i] = outNode; undrawnIDs.remove(coordinates); } // connect each output neuron to each neuron in the previous layer for(DirectedNode outNode : outputNeurons) { for(Edge e : functionalUnits.get(functionalUnits.size()-1)) { e.setOutput(outNode); outNode.addInput(e); } } // initialize the adjacency matrix adjacencyMatrix = new Double[nodes.size()][nodes.size()]; for(int i = 0; i < nodes.size(); i++) { for(int j = 0; j < nodes.size(); j++) { if(i == j) { adjacencyMatrix[i][j] = 0.0; } else { adjacencyMatrix[i][j] = Double.MAX_VALUE; } } } // fill in the adjacency matrix for(int i = 0; i < nodes.size(); i++) { Node n1 = nodes.get(i); for(int j = 0; j < nodes.size(); j++) { Node n2 = nodes.get(j); for(Edge e : n1.getOutputs()) { if(e.getOutput().equals(n2)) { adjacencyMatrix[i][j] = e.getWeight(); } } for(Edge e : n2.getInputs()) { if(e.getInput().equals(n2)) { adjacencyMatrix[i][j] = e.getWeight(); } } } } // fill in the shortest paths matrix with the Floyd-Warshall algorithm shortestPaths = adjacencyMatrix; for(int i = 0; i < nodes.size(); i++) { for(int j = 0; j < nodes.size(); j++) { for(int k = 0; k < nodes.size(); k++) { Double distSum = shortestPaths[j][i] + shortestPaths[i][k]; if(distSum < shortestPaths[j][k]) { shortestPaths[j][k] = distSum; } } } } simulated = false; double elapsedTimeInSec = (System.nanoTime() - startTime) * 1.0e-9; System.out.println("Finished initializing the neural network with " + nodes.size() + " neuron nodes and " + edges.size() + " synaptic edges in " + functionalUnits.size() + " functional units in " + elapsedTimeInSec + " seconds."); //printNodes(); //printEdges(); } /** * Store new settings to determine the type of simulation to run. * @param rounds the number of iterations to run before reporting the Network's state. * @param learning whether or not learning through backpropagation should be enabled after reach round is run. */ public void setSimulationSettings(int rounds, boolean learning) { simRounds = rounds; simLearning = learning; } /** * Run a simulation of the behavior of the <code>Network</code> with the stored settings. */ public void run() { long startTime = System.nanoTime(); for(int r = 0; r < simRounds; r++) { /* * UPGRADE: add JavaFX GUI for(Circle c : MainGUI.getNodeCircleMap().values()) { c.setFill(Color.GREY); } */ boolean step = true; while(!step) { System.out.print("Run next step in simulation? "); BufferedReader inputReader = new BufferedReader(new InputStreamReader(System.in)); try { String answer = inputReader.readLine(); if(answer.equalsIgnoreCase("yes") || answer.equalsIgnoreCase("y")) { step = true; } } catch (IOException e) { } } //System.out.println("Simulating round " + r + "..."); for(Edge[] f : functionalUnits) { for(int i = 0; i < f.length; i++) { Node n = f[i].getInput(); n.sendActionPotential(f[i]); if(pane != null) { if(n.fire()) { //MainGUI.getNodeCircleMap().get(n).setFill(Color.RED); } } } } if(simLearning && idealValues.length == functionalUnits.get(functionalUnits.size()-1).length) { backpropagate(); } } simulated = true; double elapsedTimeInSec = (System.nanoTime() - startTime) * 1.0e-9; System.out.println("Finished simulating " + simRounds + " rounds of the neural network in " + elapsedTimeInSec + " seconds."); } /** * Update a single given old layer/functional unit based on feedback from the change in performance at predicting given target values. * @return the improved layer/functional unit after learning by adjusting weight according to sigmoid of performance. */ public Edge[] learn(Edge[] oldLayer, Number[] targetValues, Double scale) { Edge[] improvedLayer = oldLayer; for(int x = 0; x < improvedLayer.length; x++) { Double target = (Double) targetValues[x]; Double error = Math.abs((improvedLayer[x].getOutput().getValue() - target) / target); improvedLayer[x].setWeight(improvedLayer[x].getWeight() + (sigmoid(error) * improvedLayer[x].getWeight() * (learnRate/scale))); // new synaptic weight += error * old weight * scaled learning rate } return improvedLayer; } /** * Send scaled feedback throughout the neural network for learning, beginning from the output layer/functional unit and ending at the initial input layer/functional unit. */ public void backpropagate() { for(int x = functionalUnits.size()-1; x > 1; x--) { Edge[] oldLayer = functionalUnits.get(x); if(oldLayer.equals(functionalUnits.get(functionalUnits.size()-1))) { functionalUnits.set(x, learn(oldLayer, idealValues, learnScale)); } else { Edge[] targetLayer = functionalUnits.get(x+1); Double[] targetValues = new Double[targetLayer.length]; for(int y = 0; y < targetLayer.length; y++) { targetValues[y] = targetLayer[y].getOutput().getValue(); } functionalUnits.set(x, learn(oldLayer, targetValues, learnScale)); } } } /** * Prints the current value and firing threshold of each <code>Node</code> (neuron) in this <code>Network</code>. */ public void printNodes() { for(Node n : nodes) { System.out.println(n + " has a current value of " + n.getValue() + " and fires if its accumulated value is greater than or equal to " + n.getThreshold() + "."); } } /** * Prints the input and output neurons of each <code>Edge</code> (synapse) in this <code>Network</code>. */ public void printEdges() { for(Edge e : edges) { System.out.println(e.getID() + " accepts action potential input from " + e.getInput() + " and transmits it to " + e.getOutput() + "."); } } /** * Prints statistics of this <code>Network</code>. */ public String getStats() { String stats = "There are " + nodes.size() + " neuron nodes and " + edges.size() + " synaptic edges in " + functionalUnits.size() + " functional units.\n"; // Node statistics Double nodeInputSum = 0.0; Double nodeOutputSum = 0.0; Double nodeValueSum = 0.0; Double nodeThresholdSum = 0.0; Integer nodeDegreeSum = 0; Double nodeClusterSum = 0.0; Double nodeClosenessSum = 0.0; //Double nodeBetweennesSum = 0.0; // calculate all Nodes' statistics for(Node n : nodes) { nodeInputSum += n.getInputs().size(); nodeOutputSum += n.getOutputs().size(); nodeValueSum += n.getValue(); nodeThresholdSum += n.getThreshold(); nodeDegreeSum += n.getDegreeCentrality(); nodeClusterSum += n.getClusteringCoefficient(); } // calculate Network statistics Double[] nodeCloseness = new Double[nodes.size()]; //Double[] nodeBetweenness = new Double[nodes.size()]; for(int i = 0; i < nodes.size(); i++) { Double closenessSum = 0.0; //Double betweennessSum = 0.0; for(int j = 0; j < nodes.size(); j++) { if(shortestPaths[i][j] != 0 && shortestPaths[i][j] != Double.MAX_VALUE) closenessSum += shortestPaths[i][j]; } nodeCloseness[i] = closenessSum; } // print average Node statistics stats += "Each neuron has an average of " + nodeInputSum/nodes.size() + " synaptic inputs.\n"; stats += "Each neuron has an average of " + nodeOutputSum/nodes.size() + " synaptic outputs.\n"; stats += "Each neuron has an average value of " + nodeValueSum/nodes.size() + ".\n"; stats += "Each neuron has an average threshold of " + nodeThresholdSum/nodes.size() + ".\n"; stats += "The average degree centrality of all neurons in the network is " + nodeDegreeSum/nodes.size() + ".\n"; stats += "The average clustering coefficient of all neurons in the network is " + nodeClusterSum/nodes.size() + ".\n"; stats += "The average closeness centrality of all neurons in the network is " + nodeClosenessSum/nodes.size() + ".\n"; //System.out.println("The average betweenness centrality of all neurons in the network is " + nodeBetweennesSum/nodes.size() + "."); // Edge statistics Double edgeWeightSum = 0.0; for(Edge e : edges) { edgeWeightSum += e.getWeight(); } stats += "The average edge weight is " + edgeWeightSum/edges.size() + ".\n"; // Network properties from http://en.wikipedia.org/wiki/Network_science#Network_Properties on April 21, 2012 // The density D of a network is defined as a ratio of the number of edges E to the number of possible edges, given by the binomial coefficient \tbinom N2, giving D = \frac{2E}{N(N-1)}. Double density = (2*edges.size()) / (Math.pow(nodes.size(), 2) - nodes.size()); stats += "The density of the neural network is: " + density + ".\n"; // breadth-first search stats += "The distances of neurons visited in a breadth-first search from the first neuron is:\n"; Double[] bfsOrder = breadthFirstSearch((DirectedSumNode) nodes.get(0)); String bfs = ""; for(Double i : bfsOrder) { bfs += i + ", "; }; bfs = bfs.substring(0, bfs.lastIndexOf(",")); stats += bfs + "\n"; // depth-first search stats += "The distances of neurons visited in a depth-first search from the first neuron is:\n"; Integer[] dfsOrder = depthFirstSearch((DirectedSumNode) nodes.get(0)); String dfs = ""; for(Integer i : dfsOrder) { dfs += i + ", "; }; dfs = dfs.substring(0, dfs.lastIndexOf(",")); stats += dfs + "\n"; // Dijkstra's shortest path stats += "The distances of neurons visited in Dijkstra's shortest path from the first neuron is:\n"; Double[] dOrder = dijkstraShortestPath((DirectedSumNode) nodes.get(0)); String d = ""; for(Double i : dOrder) { d += i + ", "; }; d = d.substring(0, d.lastIndexOf(",")); stats += d + "\n"; // Bellman-Ford shortest path stats += "The distances of neurons visited in Bellman-Ford shortest path from the first neuron is:\n"; Double[] bfOrder = bellmanFordShortestPath((DirectedSumNode) nodes.get(0)); String bf = ""; for(Double i : bfOrder) { bf += i + ", "; }; bf = bf.substring(0, bf.lastIndexOf(",")); stats += bf + "\n"; // Floyd-Warshall shortest path /* stats += "The shortest weighted paths determined by Floyd-Warshall are: \n"; floydWarshallShortestPath((DirectedSumNode) nodes.get(0), (DirectedSumNode) nodes.get(nodes.size()-1)); for(int i = 0; i < shortestPaths.length; i++) { for(int j = 0; j < shortestPaths[i].length; j++) { stats += shortestPaths[i][j] + "\t"; } stats += "\n"; } */ if(simulated) { Double nodeFireSum = 0.0; for(Node n : nodes) { nodeFireSum += n.getNumFires(); } stats += "There were " + nodeFireSum + " neuron firings during the simulation, an average of " + nodeFireSum/nodes.size() + " per neuron.\n"; } stats += "\n"; return stats; } /** * Returns the unique ID assigned to this <code>Network</code>. * @return the ID. */ public Integer getID() { return id; } /** * Returns the statistical distribution drawn from to determine neurons' initial values and firing thresholds. * @return the statistical distribution. */ public AbstractRealDistribution getDistribution() { return dist; } /** * Returns all neurons in this <code>Network</code>. * @return the <code>Nodes</code> representing neurons. */ public ArrayList<Node> getNodes() { return nodes; } /** * Returns all synapses in this <code>Network</code>. * @return the <code>Edges</code> representing synapses. */ public ArrayList<Edge> getEdges() { return edges; } /** * Returns the unique ID assigned to this <code>Edge</code>. * @return the ID. */ public ArrayList<Edge[]> getFunctionalUnits() { return functionalUnits; } /** * Returns all input neurons in this <code>Network</code>. * @return the <code>Nodes</code> representing input neurons. */ public Node[] getInputNeurons() { return inputNeurons; } /** * Replaces existing input <code>Nodes</code> with the given ones. * @param newInputs the new input <code>Nodes</code> representing neurons. */ public void setInputNeurons(Node[] newInputs) { Edge[] currentInputs = functionalUnits.get(0); if(newInputs.length == currentInputs.length) { for(int x = 0; x < currentInputs.length; x++) { currentInputs[x].setInput(newInputs[x]); } } else { throw new InputMismatchException("Number of new input neurons must match number of existing input neurons!"); } } /** * Returns all output neurons in this <code>Network</code>. * @return the <code>Nodes</code> representing output neurons. */ public Node[] getOutputNeurons() { return outputNeurons; } /** * Replaces existing output <code>Nodes</code> with the given ones. * @param newOutputs the new output <code>Nodes</code> representing neurons. */ public void setOutputNeurons(Node[] newOutputs) { Edge[] currentOutputs = functionalUnits.get(functionalUnits.size()-1); if(newOutputs.length == currentOutputs.length) { for(int x = 0; x < currentOutputs.length; x++) { currentOutputs[x].setOutput(newOutputs[x]); } } else { throw new InputMismatchException("Number of new input neurons must match number of existing input neurons!"); } } /** * Returns the correct/ideal/target values that are used to train this <code>Network</code>. * @return the target values. */ public Number[] getTargetValues() { return idealValues; } /** * Returns the adjacency matrix of this <code>Network</code>. adjacencyMatrix[i][j]=0 iff i=j; adjacencyMatrix[i][j]=w iff an Edge exists from i to j; adjacencyMatrix[i][j]=Double.MAX_VALUE otherwise. * @return the adjacency matrix. */ public Double[][] getAdjacencyMatrix() { return adjacencyMatrix; } /** * Returns the unique ID assigned to this <code>Edge</code>. * @return the ID. */ public Double[] getErrors() { Double[] errors = new Double[idealValues.length]; String msg = "Difference between idealValue[x] - learnedOutput[x]: "; for(int i = 0; i < idealValues.length; i++) { Double err = idealValues[i].doubleValue() - getOutputNeurons()[i].getValue(); errors[i] = err; msg += err + ", "; } msg = msg.substring(0, msg.lastIndexOf(", ")); //System.out.println(msg); return errors; } /** * Returns the unique ID assigned to this <code>Edge</code>. * @return the ID. */ public Double getAverageError() { Double[] errors = getErrors(); Double sum = 0.0; for(Double e : errors) { sum += e; } return sum / errors.length; } /** * Returns the unique ID assigned to this <code>Edge</code>. * @return the ID. */ public Random getRNG() { return rng; } /** * Returns the value of the sigmoid function after operating on the given input. * @param x the input to the sigmoid function. * @return the result of the sigmoid function. */ public static Double sigmoid(Double x) { Double denominator = 1 + Math.pow(Math.E, x*-1); //Double denominator = 1 + Math.exp(-x); return 1 / denominator; } /** * Utility method to transform any numeric data into an array of Doubles. * @return the transformed data. */ public static Double[] dataToDouble(Number[] data) { Double[] result = new Double[data.length]; if(data instanceof Integer[]) { for(int i = 0; i < data.length; i++) { result[i] = new Double(data[i].intValue()); } } else if(data instanceof Long[]) { for(int i = 0; i < data.length; i++) { result[i] = new Double(data[i].longValue()); } } else if(data instanceof Short[]) { for(int i = 0; i < data.length; i++) { result[i] = new Double(data[i].shortValue()); } } else if(data instanceof Float[]) { for(int i = 0; i < data.length; i++) { result[i] = new Double(data[i].floatValue()); } } else if(data instanceof Byte[]) { for(int i = 0; i < data.length; i++) { result[i] = new Double(data[i].byteValue()); } } else { for(int i = 0; i < data.length; i++) { result[i] = new Double(data[i].doubleValue()); } } return result; } /** * Used internally by depthFirstSearch() to visit a node in the network. */ private void DFVisit(Integer id, Integer[] d, Integer[] f, Integer[] p, Integer[] s, Integer[] l) { Integer uID = id; DirectedNode u = (DirectedNode) nodes.get(uID); Integer count = 0; s[uID] = 0; d[uID] = ++count; // process neighbors for(Edge e : u.getOutputs()) { DirectedNode v = (DirectedNode) e.getOutput(); Integer vID = 0; for(int i = 0; i < nodes.size(); i++) { if(nodes.get(i).equals(v)) { vID = i; } } l[uID] = 3; if(s[vID] == -1) { l[uID] = 0; } else if(s[vID] == 0) { l[uID] = 1; } else if(d[uID] < d[vID]) { l[uID] = 2; } if(s[vID] == -1) { p[vID] = uID; DFVisit(vID, d, f, p, s, l); } s[uID] = 1; f[uID] = ++count; } } /** * Depth-first visiting of a node in the network. -1 means the node has not yet been visited; 0 means the node has been visited, but may have adjacent nodes that have not been visited; 1 means the node and all its neighbors have been visited. * Best O(nodes + edges); average O(nodes + edges); worst O(nodes + edges) * @see Heineman, Pollice and Selkow, 144 * @return Integer[] representation of the order in which nodes were visited during the search. Each index number corresponds to the output of getNodes(). */ public Integer[] depthFirstSearch(DirectedNode begin) { Integer srcID = 0; Integer n = nodes.size(); Integer[] pred = new Integer[n]; Integer[] discovered = new Integer[n]; Integer[] finished = new Integer[n]; Integer[] state = new Integer[n]; Integer[] label = new Integer[n]; for(int i = 0; i < n; i++) { pred[i] = -1; discovered[i] = -1; finished[i] = -1; state[i] = -1; label[i] = -1; if(nodes.get(i).equals(begin)) { srcID = i; } } DFVisit(srcID, pred, discovered, finished, state, label); for(int u = 0; u < n; u++) { if(state[u] == -1) { DFVisit(u, pred, discovered, finished, state, label); } } return finished; } /** * Depth-first search of the network. * Best O(branching * depth); average O(branching * depth); worst O(branching * depth) * @see Heineman, Pollice and Selkow, 182. * @return Integer the distance from begin to end. */ public Double depthFirstSearch(DirectedNode begin, DirectedNode end) { Double dist = 0.0; if(begin.equals(end)) { return dist; } Stack<DirectedNode> open = new Stack<DirectedNode>(); HashSet<DirectedNode> closed = new HashSet<DirectedNode>(); open.push(begin); while(open.size() > 0) { DirectedNode n = open.pop(); closed.add(n); for(Edge e : n.getOutputs()) { DirectedNode m = (DirectedNode) e.getOutput(); if(!closed.contains(m)) { dist += e.getWeight(); if(n.equals(end)) { return dist; } if(dist < DEFAULT_SEARCH_DIST) { open.push(m); } } } } return -1.0; } /** * Breadth-first search of the network. * @see Heineman, Pollice and Selkow, 150. * Best O(nodes + edges); average O(nodes + edges); worst O(nodes + edges) * @return Integer[] representation of the order in which nodes were visited during the search. Each index number corresponds to the output of getNodes(). */ public Double[] breadthFirstSearch(DirectedNode begin) { Integer srcID = 0; Integer n = nodes.size(); Integer[] pred = new Integer[n]; Double[] dist = new Double[n]; Integer[] state = new Integer[n]; for(int i = 0; i < n; i++) { pred[i] = -1; dist[i] = Double.MAX_VALUE; state[i] = -1; if(nodes.get(i).equals(begin)) { srcID = i; } } dist[srcID] = 0.0; state[srcID] = 0; ConcurrentLinkedQueue<DirectedNode> queue = new ConcurrentLinkedQueue<DirectedNode>(); queue.add(begin); while(queue.size() > 0) { DirectedNode u = queue.remove(); Integer uID = 0; for(int i = 0; i < n; i++) { if(nodes.get(i).equals(u)) { uID = i; } } for(Edge e : u.getOutputs()) { DirectedNode v = (DirectedNode) e.getOutput(); Integer vID = 0; for(int i = 0; i < n; i++) { if(nodes.get(i).equals(v)) { vID = i; } } if(state[vID] == -1) { dist[vID] = dist[uID] + e.getWeight(); pred[vID] = uID; state[vID] = 0; queue.add(v); } } state[uID] = 1; } return dist; } /** * Breadth-first search of the network. * @see Heineman, Pollice and Selkow, 190. * Best O(branching^depth); average O(branching^depth); worst O(branching^depth) * @return Integer the distance from begin to end. */ public Double breadthFirstSearch(DirectedNode begin, DirectedNode end) { Double dist = 0.0; if(begin.equals(end)) { return dist; } ConcurrentLinkedQueue<DirectedNode> open = new ConcurrentLinkedQueue<DirectedNode>(); open.add(begin); HashSet<DirectedNode> closed = new HashSet<DirectedNode>(); while(open.size() > 0) { DirectedNode n = open.remove(); closed.add(n); for(Edge e : n.getOutputs()) { DirectedNode m = (DirectedNode) e.getOutput(); if(!closed.contains(m)) { dist += e.getWeight(); if(n.equals(end)) { return dist; } } } } return -1.0; } /** * Dijkstra's single-source shortest path algorithm for dense graphs. * @see Heineman, Pollice and Selkow, 158. * Best O(nodes^2 + edges); average O(nodes^2 + edges); worst O(nodes^2 + edges) * @return Integer[] representation of the order in which nodes were visited during the search. Each index number corresponds to the output of getNodes(). */ public Double[] dijkstraShortestPath(DirectedNode begin) { Integer uID = 0; Integer n = nodes.size(); Integer[] pred = new Integer[n]; Double[] dist = new Double[n]; Boolean[] visit = new Boolean[n]; for(int i = 0; i < n; i++) { pred[i] = -1; dist[i] = Double.MAX_VALUE; visit[i] = false; if(nodes.get(i).equals(begin)) { uID = i; } } dist[uID] = 0.0; DirectedNode u = begin; Double sd = Double.MAX_VALUE; for(int i = 0; i < n; i++) { if(!visit[i] && dist[i] < sd) { sd = dist[i]; uID = i; } if(uID == -1) { break; } visit[uID] = true; for(Integer vID = 0; vID < u.getOutputs().size(); vID++) { Double newLen = dist[uID] + u.getOutputs().get(vID).getWeight(); if(newLen < dist[vID]) { dist[vID] = newLen; pred[vID] = uID; } } } return dist; } /** * Bellman-Ford single-source shortest path algorithm. * @see Heineman, Pollice and Selkow, 162. * Best O(nodes * edges); average O(nodes * edges); worst O(nodes * edges) * @return Integer[] representation of the order in which nodes were visited during the search. Each index number corresponds to the output of getNodes(). */ public Double[] bellmanFordShortestPath(DirectedNode begin) { Integer uID = 0; Integer n = nodes.size(); Integer[] pred = new Integer[n]; Double[] dist = new Double[n]; for(int i = 0; i < n; i++) { pred[i] = -1; dist[i] = Double.MAX_VALUE; if(nodes.get(i).equals(begin)) { uID = i; } } dist[uID] = 0.0; DirectedNode u = begin; for(int i = 1; i < n; i++) { Boolean leaveEarly = true; for(uID = 0; uID < n; uID++) { for(Integer vID = 0; vID < u.getOutputs().size(); vID++) { Double newLen = dist[uID] + u.getOutputs().get(vID).getWeight(); if(newLen < dist[vID]) { dist[vID] = newLen; pred[vID] = uID; leaveEarly = false; } } } if(leaveEarly) { break; } } return dist; } /** * Floyd-Warshall all-sources shortest path algorithm. * @see Heineman, Pollice and Selkow, 166. * Best O(nodes^3); average O(nodes^3); worst O(nodes^3) * @return Integer[] representation of the order in which nodes were visited during the search. Each index number corresponds to the output of getNodes(). */ public void floydWarshallShortestPath(DirectedNode begin, DirectedNode end) { Integer uID = 0; Integer vID = 0; Integer n = nodes.size(); Integer[][] pred = new Integer[n][n]; Double[][] dist = new Double[n][n]; for(uID = 0; uID < n; uID++) { DirectedNode u = (DirectedNode) nodes.get(uID); for(vID = 0; vID < n; vID++) { dist[uID][vID] = Double.MAX_VALUE; pred[uID][vID] = -1; } dist[uID][uID] = 0.0; for(vID = 0; vID < u.getOutputs().size(); vID++) { dist[uID][vID] = u.getOutputs().get(vID).getWeight(); pred[uID][vID] = uID; } } for(int k = 0; k < n; k++) { for(int i = 0; i < n; i++) { if(dist[i][k] == Integer.MAX_VALUE) { continue; } for(int j = 0; j < n; j++) { Double newLen = dist[i][k] + dist[k][j]; if(newLen < dist[i][j]) { dist[i][j] = newLen; pred[i][j] = pred[k][j]; } } } } shortestPaths = dist; /** * re-create the path Stack<Integer> path = new Stack<Integer>(); for(int i = 0; i < n; i++) { if(nodes.get(i).equals(begin)) { uID = i; } else if(nodes.get(i).equals(end)) { vID = i; } } while(begin != end) { if(pred[uID][vID] == -1) { path = new Stack<Integer>(); } path.push(pred[uID][vID]); } return (Integer[]) path.toArray(); */ } /** * A*Search algorithm * @see Heineman, Pollice and Selkow, 195. * Best O(branching * depth); average O(branching^depth); worst O(branching^depth) */ /** * Ford-Fulkerson maximum network flow algorithm. * @see Heineman, Pollice and Selkow, 230. * Best O(edges * maxFlow); average O(edges * maxFlow); worst O(edges * maxFlow) */ }