Example #1
0
        /// <summary>
        /// Create an array of FastConnection(s) representing the connectivity of the provided INetworkDefinition.
        /// </summary>
        private static FastConnection[] CreateFastConnectionArray(INetworkDefinition networkDef)
        {
            // We vary the decode logic depending on the size of the genome. The most CPU intensive aspect of
            // decoding is the conversion of the neuron IDs at connection endpoints into neuron indexes. For small
            // genomes we simply use the BinarySearch() method on NeuronGeneList for each lookup; Each lookup is
            // an operation with O(log n) time complexity. Thus for C connections and N neurons the number of operations
            // to perform all lookups is approximately = 2*C*Log(N)
            //
            // For larger genomes we invest time in building a Dictionary that maps neuron IDs to their indexes, this on the
            // basis that the time invested will be more than recovered in time saved performing lookups; The time complexity
            // of a dictionary lookup is near constant O(1). Thus number of operations is approximately = O(2*C*1) + the time
            // required to build the dictionary which is approximately O(N).
            //
            // Therefore the choice of lookup type is based on which of these two expressions gives the lowest value.
            //
            //      Binary search.      LookupOps = 2 * C * Log2(N) * x
            //      Dictionary Search.  LookupOps = (N * y) + (2 * C * z)
            //
            // Where x, y and z are constants that adjust for the relative speeds of the lookup and dictionary building operations.
            // Note that the actual time required to perform these separate algorithms is actually a far more complex problem, and
            // for modern CPUs exact times cannot be calculated because of large memory caches and superscalar architecture that
            // makes execution times in a real environment effectively non-deterministic. Thus these calculations are a rough
            // guide/heuristic that estimate which algorithm will perform best. The constants can be found experimentally but will
            // tend to vary depending on factors such as CPU and memory architecture, .Net framework version and what other tasks the
            // CPU is currently doing which may affect our utilisation of memory caches.
            // TODO: Experimentally determine reasonably good values for constants x,y and z in some common real world runtime platform.
            IConnectionList connectionList  = networkDef.ConnectionList;
            INodeList       nodeList        = networkDef.NodeList;
            int             connectionCount = connectionList.Count;
            int             nodeCount       = nodeList.Count;

            FastConnection[] fastConnectionArray = new FastConnection[connectionCount];

            if ((2.0 * connectionCount * Math.Log(nodeCount, 2.0)) < ((2.0 * connectionCount) + nodeCount))
            {
                // Binary search requires items to be sorted.
                Debug.Assert(nodeList.IsSorted());

                // Loop the connections and lookup the neuron IDs for each connection's end points using a binary search
                // on nGeneList. This is probably the quickest approach for small numbers of lookups.
                for (int i = 0; i < connectionCount; i++)
                {
                    INetworkConnection conn = connectionList[i];
                    fastConnectionArray[i]._srcNeuronIdx = nodeList.BinarySearch(conn.SourceNodeId);
                    fastConnectionArray[i]._tgtNeuronIdx = nodeList.BinarySearch(conn.TargetNodeId);
                    fastConnectionArray[i]._weight       = conn.Weight;

                    // Check that the correct neuron indexes were found.
                    Debug.Assert(
                        nodeList[fastConnectionArray[i]._srcNeuronIdx].Id == conn.SourceNodeId &&
                        nodeList[fastConnectionArray[i]._tgtNeuronIdx].Id == conn.TargetNodeId);
                }
            }
            else
            {
                // Build dictionary of neuron indexes keyed on neuron innovation ID.
                Dictionary <uint, int> neuronIndexDictionary = new Dictionary <uint, int>(nodeCount);
                for (int i = 0; i < nodeCount; i++)
                {
                    // ENHANCEMENT: Check if neuron innovation ID requires further manipulation to make a good hash code.
                    neuronIndexDictionary.Add(nodeList[i].Id, i);
                }

                // Loop the connections and lookup the neuron IDs for each connection's end points using neuronIndexDictionary.
                // This is probably the quickest approach for large numbers of lookups.
                for (int i = 0; i < connectionCount; i++)
                {
                    INetworkConnection conn = connectionList[i];
                    fastConnectionArray[i]._srcNeuronIdx = neuronIndexDictionary[conn.SourceNodeId];
                    fastConnectionArray[i]._tgtNeuronIdx = neuronIndexDictionary[conn.TargetNodeId];
                    fastConnectionArray[i]._weight       = conn.Weight;

                    // Check that the correct neuron indexes were found.
                    Debug.Assert(
                        nodeList[fastConnectionArray[i]._srcNeuronIdx].Id == conn.SourceNodeId &&
                        nodeList[fastConnectionArray[i]._tgtNeuronIdx].Id == conn.TargetNodeId);
                }
            }

            return(fastConnectionArray);
        }