/// <summary> /// Override that paints nodes with a fill color that represents each node's activation function. /// </summary> protected override void PaintNode(GraphNode node, PaintState state) { Point nodePos = ModelToViewport(node.Position, state); if(!IsPointWithinViewport(nodePos, state)) { // Skip node. It's outside the viewport area. return; } // Paint the node as a square. Create a Rectangle that represents the square's position and size. Point p = new Point(nodePos.X - state._nodeDiameterHalf, nodePos.Y - state._nodeDiameterHalf); Size s = new Size(state._nodeDiameter, state._nodeDiameter); Rectangle r = new Rectangle(p, s); // Paint the node. Fill first and then border, this gives a clean border. Graphics g = state._g; int activationFnId = (int)node.AuxData[0]; Brush fillBrush = _brushNodeFillArr[activationFnId % _brushNodeFillArr.Length]; g.FillRectangle(fillBrush, r); g.DrawRectangle(__penBlack, r); // Draw the node tag. nodePos.X += state._nodeDiameterHalf+1; nodePos.Y -= state._nodeDiameterHalf/2; g.DrawString(node.Tag, __fontNodeTag, __brushBlack, nodePos); }
private void UpdateModelBounds(GraphNode node, ref Size bounds) { if(node.Position.X > bounds.Width) { bounds.Width = node.Position.X; } if(node.Position.Y > bounds.Height) { bounds.Height = node.Position.Y; } }
/// <summary> /// Create an IOGraph that represents the structure described by the provided INetworkDefinition. /// </summary> public IOGraph CreateGraph(INetworkDefinition networkDef) { // Perform depth analysis of network. NetworkDepthInfo depthInfo; if(networkDef.IsAcyclic) { AcyclicNetworkDepthAnalysis depthAnalysis = new AcyclicNetworkDepthAnalysis(); depthInfo = depthAnalysis.CalculateNodeDepths(networkDef); } else { CyclicNetworkDepthAnalysis depthAnalysis = new CyclicNetworkDepthAnalysis(); depthInfo = depthAnalysis.CalculateNodeDepths(networkDef); } // Create an IOGraph, allocating storage for the node lists. INodeList nodeList = networkDef.NodeList; int nodeCount = nodeList.Count; int inputCount = networkDef.InputNodeCount + 1; // + to count bias as an input layer node. int outputCount = networkDef.OutputNodeCount; int hiddenCount = nodeCount - (inputCount + outputCount); IOGraph ioGraph = new IOGraph(inputCount, outputCount, hiddenCount, 0f, depthInfo._networkDepth); // We also build a dictionary of nodes keyed by innovation ID. This is used later // to assign connections to nodes. Dictionary<uint, GraphNode> nodeDict = new Dictionary<uint,GraphNode>(nodeCount); // Loop input nodes. int idx = 0; for(int i=0; i<inputCount; i++, idx++) { // Create node, assign it a tag and add it to the node dictionary and the // input node list of the IOGraph. uint innovationId = nodeList[idx].Id; GraphNode node = new GraphNode(innovationId.ToString()); node.AuxData = CreateGraphNodeAuxData(nodeList[idx]); node.Depth = depthInfo._nodeDepthArr[idx]; nodeDict.Add(innovationId, node); ioGraph.InputNodeList.Add(node); } // Loop output nodes. for(int i=0; i<outputCount; i++, idx++) { // Create node, assign it a tag and add it to the node dictionary and the // output node list of the IOGraph. uint innovationId = nodeList[idx].Id; GraphNode node = new GraphNode(innovationId.ToString()); node.AuxData = CreateGraphNodeAuxData(nodeList[idx]); node.Depth = depthInfo._nodeDepthArr[idx]; nodeDict.Add(innovationId, node); ioGraph.OutputNodeList.Add(node); } // Loop hidden nodes. for(; idx<nodeCount; idx++) { // Create node, assign it a tag and add it to the node dictionary and the // hidden node list of the IOGraph. uint innovationId = nodeList[idx].Id; GraphNode node = new GraphNode(innovationId.ToString()); node.AuxData = CreateGraphNodeAuxData(nodeList[idx]); node.Depth = depthInfo._nodeDepthArr[idx]; nodeDict.Add(innovationId, node); ioGraph.HiddenNodeList.Add(node); } // Loop connections. Build GraphConnection objects and connect them to their source // and target nodes. double maxAbsWeight = 0.1; // Start at a non-zero value to prevent possibility of a divide by zero occurring. IConnectionList connectionList = networkDef.ConnectionList; int connCount = connectionList.Count; for(int i=0; i<connCount; i++) { // Create connection object and assign its source and target nodes. INetworkConnection connection = connectionList[i]; GraphNode sourceNode = nodeDict[connection.SourceNodeId]; GraphNode targetNode = nodeDict[connection.TargetNodeId]; GraphConnection conn = new GraphConnection(sourceNode, targetNode, (float)connection.Weight); // Add the connection to the connection lists on the source and target nodes. sourceNode.OutConnectionList.Add(conn); targetNode.InConnectionList.Add(conn); // Track weight range over all connections. double absWeight = Math.Abs(connection.Weight); if(absWeight > maxAbsWeight) { maxAbsWeight = absWeight; } } ioGraph.ConnectionWeightRange = (float)maxAbsWeight; return ioGraph; }
/// <summary> /// Gets the state object for a given graph node. Creates the object if it does not yet exist. /// </summary> public ConnectionPointInfo GetNodeStateInfo(GraphNode node) { ConnectionPointInfo info; if(!_nodeStateDict.TryGetValue(node, out info)) { info = new ConnectionPointInfo(); _nodeStateDict.Add(node, info); } return info; }
/// <summary> /// Layout nodes evenly spaced out on a grid. /// </summary> /// <param name="graph">The network/graph structure to be laid out.</param> /// <param name="layoutArea">The area the structure is to be laid out on.</param> public void Layout(IOGraph graph, Size layoutArea) { // Size struct to keep track of the area that bounds all nodes. Size bounds = new Size(0, 0); // Some other useful variables. int inputCount = graph.InputNodeList.Count; int outputCount = graph.OutputNodeList.Count; int hiddenCount = graph.HiddenNodeList.Count; double heightWidthRatio = (double)layoutArea.Height / (double)layoutArea.Width; // Determine how many layers/rows to arrange the nodes into. // Note. we always show the inputs and outputs in their own rows, therefore this just // handles how many additional rows are required for the hidden nodes. int numLayersHidden = 0; if (hiddenCount > 0) { // Arrange nodes in a square and adjust for height/width ratio of layout area. double sqrtHidden = Math.Sqrt(hiddenCount); numLayersHidden = (int)Math.Floor(sqrtHidden * heightWidthRatio); // Must be at least 1 layer if we have nodes. numLayersHidden = Math.Max(1, numLayersHidden); } // Arrange the nodes. int yMarginTop = (int)(layoutArea.Height * MarginProportionYTop); int yMarginBottom = (int)(layoutArea.Height * MarginProportionYBottom); int layoutHeight = layoutArea.Height - (yMarginTop + yMarginBottom); int yCurrent = yMarginTop; int yIncrement; if (0 == numLayersHidden) { // Just two layers (input and output). yIncrement = layoutHeight; } else { yIncrement = layoutHeight / (numLayersHidden + 1); } // Input layer. Place all input nodes in one layer. int layoutWidth = layoutArea.Width - (2 * MarginX); int xIncrement = layoutWidth / (inputCount + 1); int xCurrent = MarginX + xIncrement; // Loop input nodes. for (int i = 0; i < inputCount; i++) { GraphNode node = graph.InputNodeList[i]; node.Position = new Point(xCurrent, yCurrent); UpdateModelBounds(node, ref bounds); xCurrent += xIncrement; } // Increment yCurrent, ready for the next layer. yCurrent += yIncrement; // Layout hidden layers. if (0 != numLayersHidden) { // Calculate the max number of nodes in any hidden layer. int layerNodesMax = (int)Math.Ceiling((double)hiddenCount / (double)numLayersHidden); // Keep track of how many nodes remain to be positioned. The last layer will have fewer // than layerNodesMax if the number of nodes isn't a square number. int nodesRemaining = hiddenCount; int nodeIdx = 0; // Loop layers. for (int i = 0; i < numLayersHidden; i++) { // Calculate the number of nodes in this layer. int layerNodeCount = Math.Min(nodesRemaining, layerNodesMax); // Position nodes in this layer. xIncrement = layoutWidth / (layerNodeCount + 1); xCurrent = MarginX + xIncrement; // Loop nodes in this layer. for (int j = 0; j < layerNodeCount; j++, nodeIdx++) { GraphNode node = graph.HiddenNodeList[nodeIdx]; node.Position = new Point(xCurrent, yCurrent); UpdateModelBounds(node, ref bounds); xCurrent += xIncrement; } // Increment yCurrent, ready for the next layer. yCurrent += yIncrement; nodesRemaining -= layerNodeCount; } } // Output layer. Place all output nodes in one layer. xIncrement = layoutWidth / (outputCount + 1); xCurrent = MarginX + xIncrement; // Loop output nodes. for (int i = 0; i < outputCount; i++) { GraphNode node = graph.OutputNodeList[i]; node.Position = new Point(xCurrent, yCurrent); UpdateModelBounds(node, ref bounds); xCurrent += xIncrement; } // Store the bounds of the graph elements. Useful when drawing the graph. graph.Bounds = bounds; }