├── LICENSE ├── README.md ├── archiver ├── file.go └── null.go ├── attributes.go ├── comparer └── classic.go ├── connection.go ├── context.go ├── crosser └── classic.go ├── decoder ├── classic.go ├── eshyperneat.go ├── hyperneat.go ├── phenome.go └── substrate.go ├── experiment.go ├── generator ├── classic.go ├── common.go ├── realtime.go └── seed.go ├── genome.go ├── helpers.go ├── innovation.go ├── mutator ├── activation.go ├── classic.go ├── complete.go ├── complexify.go ├── phased.go ├── pruning.go ├── trait.go └── weight.go ├── network.go ├── network └── classic.go ├── node.go ├── result ├── classic.go └── novelty.go ├── searcher ├── concurrent.go ├── novelty.go └── serial.go ├── speciater ├── classic.go └── dynamic.go ├── types.go ├── visualizer ├── null.go └── web.go └── x ├── examples ├── boxes │ ├── boxes-eshyperneat-config.json │ ├── boxes-hyperneat-config.json │ ├── boxes-neat-config.json │ ├── boxes-pneat-config.json │ ├── boxes.go │ └── hyperneat.go ├── maze │ ├── classic.go │ ├── hard_maze.txt │ ├── maze-0-end-points-fit.svg │ ├── maze-0-end-points-nov.svg │ ├── maze-0-end-points.svg │ ├── maze-config.json │ ├── maze-realtime-config.json │ ├── maze.go │ ├── medium_maze.txt │ ├── realtime.go │ └── run.go ├── ocr │ ├── letters.go │ ├── ocr-config.json │ └── ocr.go └── xor │ ├── xor-config.json │ └── xor.go ├── proof ├── Readme.md ├── eshyperneat.go ├── hyperneat.go ├── neat.go ├── novelty.go ├── phased.go ├── proof.go ├── realtime.go └── settings.go ├── starter ├── context.go ├── experiment.go ├── identify.go └── settings.go └── trials └── run.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Update 2 | This project has been [discontinued](https://medium.com/@hummerb/evo-by-klokare-new-library-same-concept-9eff96126ec0#.rywgvow3a) here and continued under a new [project called EVO](https://github.com/klokare/evo) by klokare. 3 | 4 | 5 | 6 | RedQ.NEAT 7 | ========== 8 | This a Go implementation of NeuralEvolution of Augmenting Topologies (NEAT). From the [NEAT F.A.Q](http://www.cs.ucf.edu/~kstanley/neat.html#FAQ1). 9 | 10 | *NEAT stands for NeuroEvolution of Augmenting Topologies. It is a method for evolving artificial neural networks with a genetic algorithm. NEAT implements the idea that it is most effective to start evolution with small, simple networks and allow them to become increasingly complex over generations. That way, just as organisms in nature increased in complexity since the first cell, so do neural networks in NEAT. This process of continual elaboration allows finding highly sophisticated and complex neural networks.* 11 | 12 | More information will be provided on the blog [redq.me](http://www.redq.me). 13 | 14 | # Installation 15 | ```sh 16 | go get github.com/rqme/neat 17 | ``` 18 | 19 | # Usage 20 | The API documentation can be found at [GoDoc](http://godoc.org/github.com/rqme/neat). 21 | 22 | The Context and Experiment are the central components of the library. The latter encapsulates everything needed for execution and the former provides access to all the necessary helpers. There are several convenience functions in the starter package. 23 | 24 | RedQ.NEAT includes several demonstration experiments, each built at the onset of adding a new feature (like [phased mutation](http://sharpneat.sourceforge.net/phasedsearch.html)) or concept (like [Novelty Search](http://eplex.cs.ucf.edu/noveltysearch/userspage/)). These proof-of-concepts are intended to valid this library with the idea being tested as well as compare different helpers (such as HyperNEAT vs regular NEAT). The experiments are each in their own package in the x/experiments directory. 25 | 26 | ## Running experiments 27 | Each experiment builds off the trials package which provides a way to compare multiple runs of an experiment against each other. This package provides several command line arguments that are common to all experiments and displays its output in the console window. For example, here is the output of the XOR experiment: 28 | 29 | ```sh 30 | $ xor --check-stop --trials 40 31 | Run Iters. Seconds Nodes Conns Fitness Fail Comment 32 | --- --------- --------- --------- --------- --------- ------ --------- 33 | 0 28 1.339 9 16 16.000 34 | 1 26 1.192 8 14 15.443 35 | 2 59 3.384 7 17 16.000 36 | 3 59 3.609 14 28 16.000 37 | ... 38 | 36 45 2.513 11 20 16.000 39 | 37 30 1.265 7 12 12.250 40 | 38 28 1.246 9 17 16.000 41 | 39 19 0.822 6 12 13.930 42 | 43 | Summary for trials excluding failures (and time for skipped) 44 | Iters. Seconds Nodes Conns Fitness 45 | --- --------- --------- --------- --------- --------- 46 | AVG 34 1.769 9 17 14.894 47 | MED 32 1.625 9 16 15.996 48 | SDV 13 0.905 2 5 1.637 49 | MIN 9 0.303 5 7 10.782 50 | MAX 66 4.503 15 33 16.000 51 | ``` 52 | 53 | ### Common command-line arguments 54 | flag | description | default 55 | -----|-------------|------------ 56 | config-name | Common name used as a prefix to archive files | defaults to the name of the executable 57 | config-path | Directory containing the initial configuration file and, if available, state files | Current directory 58 | trials | The number of trial runs to perform | 10 59 | check-stop | Experiments which do not end with an explicit stop are considered to have failed. | false 60 | show-work | Informs the Evaluator (if it implements Demonstrable) to show its work during evaluation. This is used only for the best genome. | false 61 | skip-evolve | Skips evolution and only performs summary of archived runs. Best used with --show-work and setting the config-path to the ArchivePath used in the settings file. | false 62 | 63 | ## Experiments 64 | ### XOR 65 | [Exclusive OR](https://en.wikipedia.org/wiki/Exclusive_or), or XOR for short, is the starter experiment to verify the NEAT (called Classic in RedQ.NEAT) functionality. Located in the x/examples/xor directory, the package produces a standalone executable file. A configuration file, xor-config.json, is provided. 66 | 67 | The experiment provides no new command-line arguments but it is recommended to use --check-stop when running to catch trials that do not produce a solution. 68 | 69 | RedQ.NEAT was able to find a solution in 40 out of 40 trials. The median number of nodes and connections were 9 and 16 respectively. The results of this experiment are detailed in the [wiki](https://github.com/rqme/neat/wiki/XOR-experiment-results). 70 | 71 | # Background 72 | The core of this library, often called Classic in the code, was written from the ground up using Dr. Kenneth Stanley's [PhD dissertation](http://nn.cs.utexas.edu/keyword?stanley:phd04) as a guide. NEAT has changed a bit since that paper and I have made some adjustments based on the F.A.Q. I have also add some flexibility in the design to allow for growing the library via helpers which will provide for adding HyperNEAT, Novelty Search, etc. to the library without changing the core API. 73 | 74 | The library and proof-of-concept experiments utilize SVG to visualize the network of the best genome as well as the experiment's history. This visualization is based on the [NeuroEvolution Visualization Toolkit (NEVT)](http://nevt.sourceforge.net). Each image is output into an .html file for viewing from your desktop or presented through a web server. 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /archiver/file.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package archiver 28 | 29 | import ( 30 | "encoding/json" 31 | "fmt" 32 | "os" 33 | "path" 34 | "strconv" 35 | 36 | "github.com/rqme/neat" 37 | ) 38 | 39 | type FileSettings interface { 40 | ArchiveName() string 41 | ArchivePath() string 42 | } 43 | 44 | type File struct { 45 | FileSettings 46 | useTrials bool 47 | trialNum int 48 | } 49 | 50 | func (a *File) SetTrial(t int) error { 51 | a.useTrials = true 52 | a.trialNum = t 53 | return nil 54 | } 55 | 56 | func (a *File) makePath(s string) string { 57 | p := a.ArchivePath() 58 | if a.useTrials { 59 | p = path.Join(p, strconv.Itoa(a.trialNum)) 60 | } 61 | return path.Join(p, fmt.Sprintf("%s-%s.json", a.ArchiveName(), s)) 62 | } 63 | 64 | func (a *File) Archive(ctx neat.Context) error { 65 | 66 | // Save the settings 67 | name := a.makePath("config") 68 | f, err := os.Create(name) 69 | if err != nil { 70 | return err 71 | } 72 | e := json.NewEncoder(f) 73 | if err = e.Encode(ctx); err != nil { 74 | f.Close() 75 | return err 76 | } 77 | f.Close() 78 | 79 | // Save the state values 80 | for k, v := range ctx.State() { 81 | name := a.makePath(k) 82 | f, err = os.Create(name) 83 | if err != nil { 84 | return err 85 | } 86 | e = json.NewEncoder(f) 87 | if err = e.Encode(v); err != nil { 88 | f.Close() 89 | return err 90 | } 91 | f.Close() 92 | } 93 | return nil 94 | } 95 | 96 | func (a *File) Restore(ctx neat.Context) error { 97 | 98 | // Restore the settings 99 | name := a.makePath("config") 100 | f, err := os.Open(name) 101 | if err != nil { 102 | return err 103 | } 104 | d := json.NewDecoder(f) 105 | if err = d.Decode(&ctx); err != nil { 106 | f.Close() 107 | return err 108 | } 109 | f.Close() 110 | 111 | // Restore the state values 112 | for k, v := range ctx.State() { 113 | name := a.makePath(k) 114 | if _, err := os.Stat(name); os.IsNotExist(err) { 115 | continue 116 | } 117 | 118 | f, err = os.Open(name) 119 | if err != nil { 120 | return err 121 | } 122 | d = json.NewDecoder(f) 123 | if err = d.Decode(&v); err != nil { 124 | f.Close() 125 | return err 126 | } 127 | f.Close() 128 | } 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /archiver/null.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package archiver 28 | 29 | import "github.com/rqme/neat" 30 | 31 | type Null struct{} 32 | 33 | func (a Null) Archive(ctx neat.Context) error { 34 | return nil 35 | } 36 | 37 | func (a Null) Restore(ctx neat.Context) error { 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /attributes.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package neat 28 | 29 | // Provides setup actions in the helper's lifestyle 30 | type Setupable interface { 31 | // Sets up the helper 32 | Setup() error 33 | } 34 | 35 | // Provides takedown actions in the helper's lifecycle 36 | type Takedownable interface { 37 | // Takes down the helper 38 | Takedown() error 39 | } 40 | 41 | type OriginalFitnessable interface { 42 | OriginalFitness() float64 43 | } 44 | 45 | // A helper that would like to see the population 46 | type Populatable interface { 47 | // Provides the population to the helper 48 | SetPopulation(Population) error 49 | } 50 | 51 | // A helper that would like to see the active phenomes 52 | type Phenomable interface { 53 | // Provides the phenomes to the helper 54 | SetPhenomes(Phenomes) error 55 | } 56 | 57 | // Behaviorable describes an item tracks behaviors expressed during evaluation 58 | type Behaviorable interface { 59 | // Returns the expressed behaviors 60 | Behavior() []float64 61 | } 62 | 63 | // Demonstrable indicates that a helper is capable of showing (or hiding) detail of its work during 64 | // execution. This is typically employed by an evaluator and turned on when evaluating the "best" 65 | // genome after the evolution is completed. 66 | type Demonstrable interface { 67 | // ShowWork toggles the flag informing the helper to show (or) not the details during execution 68 | ShowWork(bool) 69 | } 70 | 71 | // Contextable describes an item that can receive a context 72 | type Contextable interface { 73 | SetContext(Context) error 74 | } 75 | 76 | type Crossoverable interface { 77 | SetCrossover(bool) error 78 | } 79 | 80 | type Trialable interface { 81 | SetTrial(int) error 82 | } 83 | 84 | type Improvable interface { 85 | Improvement() float64 86 | } 87 | -------------------------------------------------------------------------------- /comparer/classic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package comparer 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | 32 | "math" 33 | ) 34 | 35 | type ClassicSettings interface { 36 | DisjointCoefficient() float64 37 | ExcessCoefficient() float64 38 | WeightCoefficient() float64 39 | } 40 | 41 | // Helper to compare two genomes similarity 42 | type Classic struct { 43 | ClassicSettings 44 | } 45 | 46 | // Compares two genomes and returns their compatibility distance 47 | // 48 | // The number of excess and disjoint genes between a pair of genomes is a natural measure of their 49 | // compatibility distance. The more disjoint two genomes are, the less evolutionary history they 50 | // share, and thus the less compatible they are. Therefore, we can measure the compatibility distance δ 51 | // of different structures in NEAT as a simple linear combination of the number of excess E and 52 | // disjoint D genes, as well as the average weight differences of matching genes W, including disabled genes: 53 | // see formula in paper 54 | // The coefficients c1, c2,and c3 allow us to adjust the importance of the three factors, and thefactor N, 55 | // the number of genes in the larger genome, normalizes for genome size (N can be set to 1 if both genomes 56 | // are small, i.e., consist of fewer than 20 genes (Stanley, 110) 57 | func (c Classic) Compare(g1, g2 neat.Genome) (float64, error) { 58 | 59 | // Determine N 60 | n := 1.0 61 | if len(g1.Conns) > len(g2.Conns) && len(g1.Conns) >= 20 { 62 | n = float64(len(g1.Conns)) 63 | } else if len(g2.Conns) >= 20 { 64 | n = float64(len(g2.Conns)) 65 | } 66 | 67 | // Ensure connections are sorted 68 | _, conns1 := g1.GenesByInnovation() 69 | _, conns2 := g2.GenesByInnovation() 70 | 71 | // Calculate the components. This assumes both genomes' connections are sorted by their 72 | // innovation number (which is true if the NEAT library created them) 73 | var d, e, w, x float64 74 | i := 0 75 | j := 0 76 | for i < len(conns1) || j < len(conns2) { 77 | switch { 78 | case i == len(conns1): 79 | e += 1 80 | j += 1 81 | case j == len(conns2): 82 | e += 1 83 | i += 1 84 | default: 85 | c1 := conns1[i] 86 | c2 := conns2[j] 87 | switch { 88 | case c1.Innovation < c2.Innovation: 89 | d += 1 90 | i += 1 91 | case c1.Innovation > c2.Innovation: 92 | d += 1 93 | j += 1 94 | default: // Same innovation number 95 | w += math.Abs(c1.Weight - c2.Weight) 96 | x += 1 97 | i += 1 98 | j += 1 99 | } 100 | } 101 | } 102 | 103 | // Return the compatibility distance 104 | n = 1 // NOTE: The variable N mentioned in the paper does not seem to be used in any implemenation 105 | δ := c.ExcessCoefficient()*e/n + c.DisjointCoefficient()*d/n 106 | if x > 0 { 107 | δ += c.WeightCoefficient() * w / x 108 | } 109 | return δ, nil 110 | } 111 | -------------------------------------------------------------------------------- /connection.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package neat 28 | 29 | import ( 30 | "bytes" 31 | "encoding/json" 32 | "fmt" 33 | "sort" 34 | ) 35 | 36 | // Definition of a synapse 37 | type Connection struct { 38 | Innovation int // Innovation number for this connection 39 | Source, Target int // Innovation numbers of the source and target nodes 40 | Weight float64 // Connection weight 41 | Enabled bool // Is this connection enabled? 42 | } 43 | 44 | func (c Connection) Key() (k InnoKey) { 45 | k[0] = float64(c.Source) 46 | k[1] = float64(c.Target) 47 | return 48 | } 49 | 50 | func (c Connection) String() string { 51 | b := bytes.NewBufferString(fmt.Sprintf("Conn %d Source %d Target %d Weight %f ", c.Innovation, c.Source, c.Target, c.Weight)) 52 | if c.Enabled { 53 | b.WriteString("Enabled") 54 | } else { 55 | b.WriteString("Disabled") 56 | } 57 | return b.String() 58 | } 59 | 60 | type Connections map[int]Connection 61 | 62 | func (cm Connections) connsToSlice() []Connection { 63 | 64 | // Maps with non-string keys cannot be encoded. Transfer to a slice to handle this 65 | items := &sortConnsByInnovation{make([]Connection, 0, len(cm))} 66 | for _, s := range cm { 67 | items.conns = append(items.conns, s) 68 | } 69 | sort.Sort(items) 70 | return items.conns 71 | } 72 | 73 | func (cm *Connections) connsFromSlice(items []Connection) { 74 | if *cm == nil { 75 | *cm = make(map[int]Connection) 76 | } 77 | m := *cm 78 | for _, s := range items { 79 | m[s.Innovation] = s 80 | } 81 | } 82 | 83 | func (cm Connections) MarshalJSON() ([]byte, error) { 84 | items := cm.connsToSlice() 85 | buf := new(bytes.Buffer) 86 | enc := json.NewEncoder(buf) 87 | err := enc.Encode(items) 88 | return buf.Bytes(), err 89 | } 90 | 91 | func (cm *Connections) UnmarshalJSON(data []byte) error { 92 | buf := bytes.NewBuffer(data) 93 | dec := json.NewDecoder(buf) 94 | var items []Connection 95 | err := dec.Decode(&items) 96 | if err == nil { 97 | cm.connsFromSlice(items) 98 | } 99 | return err 100 | } 101 | 102 | type sortConnsByKey struct { 103 | nodeMap map[int]Node 104 | conns []Connection 105 | } 106 | 107 | func (g *sortConnsByKey) Len() int { return len(g.conns) } 108 | func (g *sortConnsByKey) Less(i, j int) bool { 109 | ti := g.nodeMap[g.conns[i].Target] 110 | tj := g.nodeMap[g.conns[j].Target] 111 | if ti.Y == tj.Y { 112 | return ti.X < tj.X 113 | } else { 114 | return ti.Y < tj.Y 115 | } 116 | } 117 | func (g *sortConnsByKey) Swap(i, j int) { g.conns[i], g.conns[j] = g.conns[j], g.conns[i] } 118 | 119 | type sortConnsByInnovation struct{ conns []Connection } 120 | 121 | func (g *sortConnsByInnovation) Len() int { return len(g.conns) } 122 | func (g *sortConnsByInnovation) Less(i, j int) bool { 123 | return g.conns[i].Innovation < g.conns[j].Innovation 124 | } 125 | func (g *sortConnsByInnovation) Swap(i, j int) { g.conns[i], g.conns[j] = g.conns[j], g.conns[i] } 126 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | package neat 27 | 28 | type Context interface { 29 | // Component helpers 30 | Archiver() Archiver 31 | Comparer() Comparer 32 | Crosser() Crosser 33 | Decoder() Decoder 34 | Evaluator() Evaluator 35 | Generator() Generator 36 | Mutator() Mutator 37 | Searcher() Searcher 38 | Speciater() Speciater 39 | Visualizer() Visualizer 40 | 41 | // State is a registry of elements to be persisted 42 | State() map[string]interface{} 43 | 44 | // Returns the next ID in the sequence 45 | NextID() int 46 | 47 | // Returns the innovation number for the gene 48 | Innovation(t InnoType, k InnoKey) int 49 | } 50 | -------------------------------------------------------------------------------- /crosser/classic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package crosser 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | 32 | "math/rand" 33 | ) 34 | 35 | type ClassicSettings interface { 36 | // Probability that a disabled connection will be renabled in the child 37 | EnableProbability() float64 38 | 39 | // Probability that the child's connection weight or trait is an average of the values from both parents 40 | MateByAveragingProbability() float64 41 | } 42 | 43 | type Classic struct { 44 | ClassicSettings 45 | } 46 | 47 | // Returns a new genome that is a cross between the two parent genomes 48 | // 49 | // When crossing over, the genes with the same innovation numbers are lined up and crossed over in 50 | // one of two ways. In the first method, matching genes are randomly chosen for the offspring 51 | // genome. Alternatively, the connection weights of matching genes can be averaged (Wright (1991) 52 | // reviews both types of crossover and their merits). NEAT uses both types of crossover. Disjoint 53 | // and excess genes are inherited from the more fit parent, or if they are equally fit, each gene 54 | // is inherited from either parent randomly. Disabled genes have a chance of being reenabled during 55 | // crossover, allowing networks to make use of older genes once again. (Stanley, 38) 56 | func (c Classic) Cross(p1, p2 neat.Genome) (child neat.Genome, err error) { 57 | rng := rand.New(rand.NewSource(rand.Int63())) 58 | 59 | // Ensure the more fit parent is first 60 | same := (p1.Fitness == p2.Fitness) 61 | if p1.Fitness < p2.Fitness { 62 | p1, p2 = p2, p1 63 | } 64 | 65 | // Create the child 66 | child = neat.Genome{} 67 | 68 | // Add the connections 69 | c.addConns(rng, same, p1, p2, &child) 70 | 71 | // Re-enable connections 72 | c.enableConns(rng, &child) 73 | 74 | // Ensure the nodes 75 | c.ensureNodes(rng, p1, p2, &child) 76 | 77 | // Set the traits 78 | c.setTraits(rng, same, p1, p2, &child) 79 | return 80 | } 81 | 82 | func (c *Classic) addConns(rng *rand.Rand, same bool, p1, p2 neat.Genome, child *neat.Genome) { 83 | child.Conns = make(map[int]neat.Connection, len(p1.Conns)) 84 | var i, j int 85 | var c1, c2 neat.Connection 86 | _, conns1 := p1.GenesByInnovation() 87 | _, conns2 := p2.GenesByInnovation() 88 | for i < len(conns1) && j < len(conns2) { 89 | c1, c2 = conns1[i], conns2[j] 90 | switch { 91 | case c1.Innovation < c2.Innovation: 92 | child.Conns[c1.Innovation] = c1 93 | i += 1 94 | 95 | case c1.Innovation > c2.Innovation: 96 | if same { 97 | child.Conns[c2.Innovation] = c2 98 | } 99 | j += 1 100 | 101 | default: // conns1[i].Innovation == conns2[j].Innovation: 102 | if rng.Float64() < c.MateByAveragingProbability() { 103 | conn := neat.Connection{ 104 | Innovation: c1.Innovation, 105 | Source: c1.Source, 106 | Target: c1.Target, 107 | Enabled: c1.Enabled, // From NEAT FAQ : In such a situation (which I have found to be rare) you may want to edit the mating code such that disabled genes are only disabled in the offspring if they are disabled in the more fit parent. This fix will keep the disabling of genes to a minimum. 108 | Weight: (c1.Weight + c2.Weight) / 2.0, 109 | } 110 | 111 | child.Conns[conn.Innovation] = conn 112 | } else { 113 | if rng.Float64() < 0.5 { 114 | child.Conns[c1.Innovation] = c1 115 | } else { 116 | child.Conns[c2.Innovation] = c2 117 | } 118 | } 119 | 120 | i += 1 121 | j += 1 122 | } 123 | } 124 | for i < len(conns1) { 125 | c1 = conns1[i] 126 | child.Conns[c1.Innovation] = c1 127 | i += 1 128 | } 129 | for same && j < len(conns2) { 130 | c2 = conns2[j] 131 | child.Conns[c2.Innovation] = c2 132 | j += 1 133 | } 134 | } 135 | 136 | // Enables connections based on probability 137 | func (c *Classic) enableConns(rng *rand.Rand, child *neat.Genome) { 138 | for k, conn := range child.Conns { 139 | if !conn.Enabled && rng.Float64() < c.EnableProbability() { 140 | conn.Enabled = true 141 | child.Conns[k] = conn 142 | } 143 | } 144 | } 145 | 146 | // Ensures that child has proper nodes for each connection 147 | func (c *Classic) ensureNodes(rng *rand.Rand, p1, p2 neat.Genome, child *neat.Genome) { 148 | child.Nodes = make(map[int]neat.Node, len(p1.Nodes)) 149 | for k, node := range p1.Nodes { 150 | child.Nodes[k] = node 151 | } 152 | for _, conn := range child.Conns { 153 | var k int 154 | for i := 0; i < 2; i++ { 155 | if i == 0 { 156 | k = conn.Source 157 | } else { 158 | k = conn.Target 159 | } 160 | if _, ok := child.Nodes[k]; !ok { 161 | node, ok := p2.Nodes[k] 162 | if !ok { 163 | panic("MISSING3") 164 | } 165 | child.Nodes[k] = node 166 | } 167 | } 168 | } 169 | } 170 | 171 | // Sets the child's traits from one or both of the parents 172 | func (c *Classic) setTraits(rng *rand.Rand, same bool, p1, p2 neat.Genome, child *neat.Genome) { 173 | child.Traits = make([]float64, len(p1.Traits)) 174 | for i := 0; i < len(child.Traits); i++ { 175 | if same { 176 | if rng.Float64() < c.MateByAveragingProbability() { 177 | child.Traits[i] = (p1.Traits[i] + p2.Traits[i]) / 2.0 178 | } else { 179 | if rng.Float64() < 0.5 { 180 | child.Traits[i] = p1.Traits[i] 181 | } else { 182 | child.Traits[i] = p2.Traits[i] 183 | } 184 | } 185 | } else { 186 | child.Traits[i] = p1.Traits[i] // Take from the more fit parent 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /decoder/classic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Brian Hummer (brian@redq.me), All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted 5 | provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions 8 | and the following disclaimer. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the documentation and/or other 10 | materials provided with the distribution. Neither the name of the nor the names of its 11 | contributors may be used to endorse or promote products derived from this software without 12 | specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 13 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 14 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 15 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 16 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 17 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 19 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 20 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 21 | */ 22 | 23 | package decoder 24 | 25 | import ( 26 | "github.com/rqme/neat" 27 | "github.com/rqme/neat/network" 28 | ) 29 | 30 | // Helper that decodes the genome into a neural network 31 | type Classic struct{} 32 | 33 | // Decodes the genome into a phenome 34 | func (d Classic) Decode(g neat.Genome) (p neat.Phenome, err error) { 35 | // Return the phenome 36 | net, e := d.decode(g) 37 | if e != nil { 38 | err = e 39 | } 40 | p = Phenome{ 41 | Genome: g, 42 | Network: net, 43 | } 44 | return 45 | } 46 | 47 | func (d Classic) decode(g neat.Genome) (net neat.Network, err error) { 48 | 49 | // Identify the genes 50 | nodes, conns := g.GenesByPosition() 51 | 52 | // Create the neurons 53 | nmap := make(map[int]int) 54 | neurons := make([]network.Neuron, len(nodes)) 55 | for i, ng := range nodes { 56 | nmap[ng.Innovation] = i 57 | neurons[i] = network.Neuron{NeuronType: ng.NeuronType, ActivationType: ng.ActivationType, X: ng.X, Y: ng.Y} 58 | } 59 | 60 | // Create the synapses 61 | //forward := true // Keep track of conenctions to determine if this is a feed-forward only network 62 | synapses := make([]network.Synapse, 0, len(conns)) 63 | for _, cg := range conns { 64 | if cg.Enabled { 65 | //src, tgt := nodes[nmap[cg.Source]], nodes[nmap[cg.Target]] 66 | //forward = forward && src.Y < tgt.Y 67 | synapses = append(synapses, network.Synapse{ 68 | Source: nmap[cg.Source], 69 | Target: nmap[cg.Target], 70 | Weight: cg.Weight, 71 | }) 72 | } 73 | } 74 | 75 | net, err = network.New(neurons, synapses) 76 | return 77 | } 78 | 79 | // Removed recurrent functionality 2015-09-15 (BSH) to simplify and improve performance. Leaving this for now in case I bring it back. 80 | func calcIters(neurons []network.Neuron, synapses []network.Synapse) int { 81 | a := make(map[float64]bool, 10) 82 | b := make(map[float64]bool, 10) 83 | for _, s := range synapses { 84 | src := neurons[s.Source] 85 | tgt := neurons[s.Target] 86 | a[tgt.Y] = true 87 | if tgt.Y <= src.Y { 88 | b[src.Y] = true 89 | } 90 | } 91 | return len(a) + len(b) 92 | } 93 | 94 | type sortnodes []neat.Node 95 | 96 | func (s sortnodes) Len() int { return len(s) } 97 | func (s sortnodes) Less(i, j int) bool { 98 | if s[i].Y == s[j].Y { 99 | return s[i].X < s[j].X 100 | } else { 101 | return s[i].Y < s[j].Y 102 | } 103 | } 104 | func (s sortnodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 105 | 106 | type sortconns struct { 107 | nodes map[int]int 108 | conns []neat.Connection 109 | } 110 | 111 | func (s sortconns) Len() int { return len(s.conns) } 112 | func (s sortconns) Less(i, j int) bool { 113 | si := s.nodes[s.conns[i].Source] 114 | ti := s.nodes[s.conns[i].Target] 115 | sj := s.nodes[s.conns[j].Source] 116 | tj := s.nodes[s.conns[j].Target] 117 | if ti == tj { 118 | return si < sj 119 | } else { 120 | return ti < tj 121 | } 122 | } 123 | func (s sortconns) Swap(i, j int) { s.conns[i], s.conns[j] = s.conns[j], s.conns[i] } 124 | -------------------------------------------------------------------------------- /decoder/hyperneat.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Brian Hummer (brian@redq.me), All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted 5 | provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions 8 | and the following disclaimer. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the documentation and/or other 10 | materials provided with the distribution. Neither the name of the nor the names of its 11 | contributors may be used to endorse or promote products derived from this software without 12 | specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 13 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 14 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 15 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 16 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 17 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 19 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 20 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 21 | */ 22 | 23 | package decoder 24 | 25 | import ( 26 | "fmt" 27 | "math" 28 | 29 | "github.com/rqme/neat" 30 | ) 31 | 32 | // Special case: 1 layer of nodes in this case, examine nodes to separate out "vitural layers" by neuron type 33 | // othewise connect every neuron in one layer to the subsequent layer 34 | 35 | type HyperNEATSettings interface { 36 | SubstrateLayers() []SubstrateNodes // Substrate definitions 37 | WeightRange() float64 // Weight range for new connections 38 | } 39 | 40 | type HyperNEAT struct { 41 | HyperNEATSettings 42 | CppnDecoder neat.Decoder 43 | } 44 | 45 | // Outputs 0..len(substrate layers) = weights. if len(outputs) = 2* that number, second set is activation function, 3rd is bias connection? Need flags for these 46 | // n = number of layers - 1 47 | // first n = weights 48 | // flags for bias oututs = 1 or 2 meaning use outputs starting at 1*n or 2*n 49 | // activation, too. 50 | 51 | func (d *HyperNEAT) Decode(g neat.Genome) (p neat.Phenome, err error) { 52 | // Validate the number of inputs and outputs 53 | if err = d.validate(g); err != nil { 54 | return 55 | } 56 | 57 | // Decode the CPPN 58 | var cppn neat.Phenome 59 | cppn, err = d.CppnDecoder.Decode(g) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | // Create a new Substrate 65 | layers := d.SubstrateLayers() 66 | ncnt := len(layers[0]) 67 | ccnt := 0 68 | for i := 1; i < len(layers); i++ { 69 | ncnt += len(layers[i]) 70 | ccnt += len(layers[i]) * len(layers[i-1]) 71 | } 72 | s := &Substrate{ 73 | Nodes: make([]SubstrateNode, 0, ncnt), 74 | Conns: make([]SubstrateConn, 0, ccnt), 75 | } 76 | 77 | // Add the nodes to the substrate 78 | i := 0 79 | for _, l := range layers { 80 | // TODO: Should I sort the nodes by position in the network? 81 | for j, n := range l { 82 | l[j].id = i 83 | s.Nodes = append(s.Nodes, n) 84 | i += 1 85 | } 86 | } 87 | 88 | // Create connections 89 | var outputs []float64 // output from the Cppn 90 | wr := d.WeightRange() 91 | for l := 1; l < len(layers); l++ { 92 | for _, src := range layers[l-1] { 93 | for _, tgt := range layers[l] { 94 | outputs, err = cppn.Activate(append(src.Position, tgt.Position...)) 95 | if err != nil { 96 | return nil, err 97 | } 98 | w := math.Abs(outputs[l-1]) 99 | if w > 0.2 { 100 | s.Conns = append(s.Conns, SubstrateConn{ 101 | Source: src.id, 102 | Target: tgt.id, 103 | Weight: math.Copysign((w-0.2)*wr/0.8, outputs[l-1]), 104 | }) 105 | } 106 | } 107 | } 108 | } 109 | 110 | // Return the new network 111 | var net neat.Network 112 | net, err = s.Decode() 113 | if err != nil { 114 | return nil, err 115 | } 116 | p = Phenome{g, net} 117 | return 118 | } 119 | 120 | func (d *HyperNEAT) validate(g neat.Genome) error { 121 | var icnt, ocnt int 122 | for _, n := range g.Nodes { 123 | if n.NeuronType == neat.Input { 124 | icnt += 1 125 | } else if n.NeuronType == neat.Output { 126 | min, max := n.ActivationType.Range() 127 | found := false 128 | switch { 129 | case math.IsNaN(min), math.IsNaN(max): 130 | found = true 131 | case min >= 0: 132 | found = true 133 | } 134 | if found { 135 | return fmt.Errorf("Invalid activation type for output: %s [%f, %f]", n.ActivationType, min, max) 136 | } 137 | ocnt += 1 138 | } 139 | } 140 | 141 | layers := d.SubstrateLayers() 142 | cnt := len(layers[0][0].Position) 143 | for i, l := range layers { 144 | for j, n := range l { 145 | if len(n.Position) != cnt { 146 | return fmt.Errorf("Inconsistent position length in substrate layer %d node %d. Expected %d but found %d.", i, j, cnt, len(n.Position)) 147 | } 148 | } 149 | } 150 | if cnt*2 < icnt { 151 | return fmt.Errorf("Insufficient number of inputs to decode substrate. Need %d but have %d", cnt*2, icnt) 152 | } 153 | 154 | if ocnt < len(layers)-1 { 155 | return fmt.Errorf("Insufficient number of outputs to decode substrate. Need %d but have %d", len(layers)-1, ocnt) 156 | } 157 | 158 | return nil 159 | } 160 | -------------------------------------------------------------------------------- /decoder/phenome.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package decoder 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | ) 32 | 33 | type Phenome struct { 34 | neat.Genome 35 | neat.Network 36 | } 37 | 38 | // Returns the identify of the underlying genome 39 | func (p Phenome) ID() int { return p.Genome.ID } 40 | 41 | // Returns trait values which could be used during evaluation 42 | func (p Phenome) Traits() []float64 { return p.Genome.Traits } 43 | 44 | // Returns the results of processing the inputs with the neural network 45 | func (p Phenome) Activate(inputs []float64) (outputs []float64, err error) { 46 | return p.Network.Activate(inputs) 47 | } 48 | -------------------------------------------------------------------------------- /decoder/substrate.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Brian Hummer (brian@redq.me), All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted 5 | provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions 8 | and the following disclaimer. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the documentation and/or other 10 | materials provided with the distribution. Neither the name of the nor the names of its 11 | contributors may be used to endorse or promote products derived from this software without 12 | specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 13 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 14 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 15 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 16 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 17 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 19 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 20 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 21 | */ 22 | 23 | package decoder 24 | 25 | import ( 26 | "bytes" 27 | "fmt" 28 | 29 | "github.com/rqme/neat" 30 | "github.com/rqme/neat/network" 31 | ) 32 | 33 | type SubstrateNode struct { 34 | id int // internal ID of node in substrate 35 | Position []float64 36 | neat.NeuronType 37 | } 38 | 39 | func (n SubstrateNode) String() string { 40 | return fmt.Sprintf("%d %v %s", n.id, n.Position, n.NeuronType) 41 | } 42 | 43 | type SubstrateNodes []SubstrateNode 44 | 45 | func (s SubstrateNodes) Contains(n SubstrateNode) bool { 46 | return s.IndexOf(n) != -1 47 | } 48 | 49 | func (s SubstrateNodes) IndexOf(n SubstrateNode) int { 50 | for j, x := range s { 51 | f := true 52 | for i := 0; i < len(x.Position); i++ { 53 | if x.Position[i] != n.Position[i] { 54 | f = false 55 | break 56 | } 57 | } 58 | if f { 59 | return j 60 | } 61 | } 62 | return -1 63 | } 64 | 65 | type SubstrateConn struct { 66 | Source, Target int // IDs of the source and target nodes 67 | Weight float64 68 | } 69 | 70 | func (c SubstrateConn) String() string { 71 | return fmt.Sprintf("%d -> %d : %f", c.Source, c.Target, c.Weight) 72 | } 73 | 74 | type SubstrateConns []SubstrateConn 75 | 76 | type Substrate struct { 77 | Nodes SubstrateNodes 78 | Conns SubstrateConns 79 | } 80 | 81 | func (s Substrate) String() string { 82 | b := bytes.NewBufferString("Substrate") 83 | b.WriteString("\n\tNodes:") 84 | for i, n := range s.Nodes { 85 | b.WriteString(fmt.Sprintf("\n\t\t%d - %s", i, n)) 86 | } 87 | b.WriteString("\n\tConns:") 88 | for i, c := range s.Conns { 89 | b.WriteString(fmt.Sprintf("\n\t\t%d - %s", i, c)) 90 | } 91 | return b.String() 92 | } 93 | func (s Substrate) Decode() (neat.Network, error) { 94 | 95 | // Create neurons from the nodes 96 | ns := make([]network.Neuron, len(s.Nodes)) 97 | nm := make(map[int]int, len(s.Nodes)) 98 | for i, sn := range s.Nodes { 99 | nm[sn.id] = i 100 | ns[i] = network.Neuron{NeuronType: sn.NeuronType} 101 | ns[i].X, ns[i].Y = sn.Position[0], sn.Position[1] // TODO: Improve this as all layers will be collapsed 102 | switch sn.NeuronType { 103 | case neat.Input, neat.Bias: 104 | ns[i].ActivationType = neat.Direct 105 | default: 106 | ns[i].ActivationType = neat.Sigmoid 107 | } 108 | } 109 | 110 | // Create synapses from the connections 111 | cs := make([]network.Synapse, len(s.Conns)) 112 | for i, sc := range s.Conns { 113 | cs[i] = network.Synapse{ 114 | Source: nm[sc.Source], 115 | Target: nm[sc.Target], 116 | Weight: sc.Weight, 117 | } 118 | } 119 | 120 | // Return the new network 121 | return network.New(ns, cs) 122 | } 123 | 124 | // Trims the substrate of connections and hidden nodes that are not part of a valid path from 125 | // input to outpout 126 | func (s *Substrate) trim() { 127 | 128 | // Iterate output neurons 129 | } 130 | -------------------------------------------------------------------------------- /experiment.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package neat 28 | 29 | import ( 30 | "bytes" 31 | "encoding/json" 32 | "fmt" 33 | "sort" 34 | 35 | . "github.com/rqme/errors" 36 | ) 37 | 38 | type ExperimentSettings interface { 39 | Iterations() int 40 | Traits() Traits 41 | FitnessType() FitnessType 42 | ExperimentName() string 43 | } 44 | 45 | // Experiment provides the definition of how to solve the problem using NEAT 46 | type Experiment struct { 47 | ExperimentSettings 48 | ctx Context 49 | 50 | // State 51 | population Population `neat:"state"` 52 | cache map[int]Phenome 53 | best Genome 54 | iteration int 55 | stopped bool 56 | } 57 | 58 | func (e *Experiment) SetContext(x Context) error { 59 | e.ctx = x 60 | e.ctx.State()["population"] = &e.population 61 | return nil 62 | } 63 | 64 | func (e Experiment) Context() Context { return e.ctx } 65 | 66 | func (e Experiment) Population() Population { return e.population } 67 | 68 | func (e Experiment) Stopped() bool { return e.stopped } 69 | 70 | func (e Experiment) Iteration() int { return e.iteration } 71 | 72 | // String returns a description of the experiment 73 | func (e Experiment) String() string { 74 | return fmt.Sprintf("Experiment %s at iteration %d has best genome %d with fitness %f", e.ExperimentName(), e.iteration, e.best.ID, e.best.Fitness) 75 | } 76 | 77 | // Runs a configured experiment. If restoring, including just the configuration, this must be done 78 | // prior to calling Run. 79 | func Run(e *Experiment) error { 80 | 81 | // Ensure this is a valid experiment 82 | if e.Iterations() < 1 { 83 | return fmt.Errorf("Invalid value for Iterations: %d", e.Iterations()) 84 | } 85 | 86 | // Iterate the experiment 87 | for e.iteration = 0; e.iteration < e.Iterations(); e.iteration++ { 88 | //fmt.Println("iteration", e.iteration, "best", e.best.Fitness) 89 | // Reset the innovation history 90 | //e.mrk.Reset() 91 | 92 | // Advance the population 93 | if err := advance(e); err != nil { 94 | return fmt.Errorf("Could not advance the population: %v", err) 95 | } 96 | 97 | // Update the phenome cache 98 | if err := updateCache(e); err != nil { 99 | return fmt.Errorf("Couuld not update cache in the experiment: %v", err) 100 | } 101 | 102 | // Evaluate the population 103 | if stop, err := search(e); err != nil { 104 | return fmt.Errorf("Error evaluating the population: %v", err) 105 | } else if stop { 106 | e.stopped = true 107 | break 108 | } 109 | } 110 | 111 | // Take one last archive and return 112 | if err := e.ctx.Archiver().Archive(e.ctx); err != nil { 113 | return fmt.Errorf("Could not take last archive of experiment: %v", err) 114 | } 115 | if err := e.ctx.Visualizer().Visualize(e.population); err != nil { 116 | return fmt.Errorf("Could not visualize the experiment for the last time: %v", err) 117 | } 118 | return nil 119 | } 120 | 121 | // Advances the experiment to the next generation 122 | func advance(e *Experiment) error { 123 | 124 | curr := e.population 125 | next, err := e.ctx.Generator().Generate(curr) 126 | if err != nil { 127 | return err 128 | } 129 | 130 | if next.Generation > curr.Generation { 131 | if err = e.ctx.Visualizer().Visualize(e.population); err != nil { 132 | return err 133 | } 134 | if err = e.ctx.Archiver().Archive(e.ctx); err != nil { 135 | return err 136 | } 137 | if err = updateSettings(e, e.best); err != nil { 138 | return err 139 | } 140 | } 141 | 142 | e.population = next 143 | return nil 144 | } 145 | 146 | // Update the settings based on the traits of a genome 147 | func updateSettings(e *Experiment, g Genome) error { 148 | cnt := 0 149 | b := bytes.NewBufferString("{") 150 | for t, trait := range e.Traits() { 151 | if trait.IsSetting { 152 | if cnt > 0 { 153 | b.WriteString(",\n") 154 | } 155 | b.WriteString(fmt.Sprintf(`"%s": %f`, trait.Name, g.Traits[t])) 156 | cnt += 1 157 | } 158 | } 159 | b.WriteString("\n}") 160 | 161 | enc := json.NewEncoder(b) 162 | return enc.Encode(&e.ctx) 163 | } 164 | 165 | // Updates the cache of phenomes 166 | func updateCache(e *Experiment) (err error) { 167 | var old map[int]Phenome 168 | if len(e.cache) == 0 { 169 | old = make(map[int]Phenome, 0) 170 | } else { 171 | old = e.cache 172 | } 173 | e.cache = make(map[int]Phenome, len(e.population.Genomes)) 174 | 175 | errs := new(Errors) 176 | pc := make(chan Phenome) 177 | cnt := 0 178 | for _, g := range e.population.Genomes { 179 | if p, ok := old[g.ID]; ok { 180 | e.cache[g.ID] = p 181 | } else { 182 | cnt += 1 183 | go func(g Genome) { 184 | p, err := e.ctx.Decoder().Decode(g) 185 | if err != nil { 186 | errs.Add(fmt.Errorf("Unable to decode genome [%d]: %v", g.ID, err)) 187 | } 188 | pc <- p 189 | }(g) 190 | } 191 | } 192 | 193 | for i := 0; i < cnt; i++ { 194 | p := <-pc 195 | if p != nil { 196 | e.cache[p.ID()] = p 197 | } 198 | 199 | } 200 | return errs.Err() 201 | } 202 | 203 | // Searches the population and updates the genomes' fitness 204 | func search(e *Experiment) (stop bool, err error) { 205 | 206 | // Map the genomes for convenience 207 | m := make(map[int]int, len(e.population.Genomes)) 208 | for i, g := range e.population.Genomes { 209 | m[g.ID] = i 210 | } 211 | 212 | // Perform the search 213 | phenomes := make([]Phenome, 0, len(e.cache)) 214 | for _, p := range e.cache { 215 | phenomes = append(phenomes, p) 216 | } 217 | for _, h := range []interface{}{e.ctx.Searcher(), e.ctx.Evaluator()} { 218 | if ph, ok := h.(Phenomable); ok { 219 | if err = ph.SetPhenomes(phenomes); err != nil { 220 | return 221 | } 222 | } 223 | if sh, ok := h.(Setupable); ok { 224 | if err = sh.Setup(); err != nil { 225 | return 226 | } 227 | } 228 | } 229 | 230 | var rs Results 231 | if rs, err = e.ctx.Searcher().Search(phenomes); err != nil { 232 | return 233 | } 234 | 235 | for _, h := range []interface{}{e.ctx.Evaluator(), e.ctx.Searcher()} { 236 | if th, ok := h.(Takedownable); ok { 237 | if err = th.Takedown(); err != nil { 238 | return 239 | } 240 | } 241 | } 242 | 243 | // Update the fitnesses 244 | var best Genome 245 | errs := new(Errors) 246 | // := make([]float64, len(e.population.Genomes)) 247 | // TODO: make this concurrent 248 | for _, r := range rs { 249 | i := m[r.ID()] 250 | if err = r.Err(); err != nil { 251 | errs.Add(fmt.Errorf("Error updating fitness for genome [%d]: %v", r.ID(), r.Err())) 252 | } 253 | e.population.Genomes[i].Fitness = r.Fitness() 254 | if imp, ok := r.(Improvable); ok { 255 | e.population.Genomes[i].Improvement = imp.Improvement() 256 | } else { 257 | e.population.Genomes[i].Improvement = e.population.Genomes[i].Fitness 258 | } 259 | //fit[i] = e.population.Genomes[i].Fitness 260 | if e.population.Genomes[i].Fitness > best.Fitness { 261 | best = e.population.Genomes[i] 262 | } 263 | stop = stop || r.Stop() 264 | } 265 | 266 | // Update the best genome 267 | if errs.Err() == nil { 268 | if e.FitnessType() == Absolute { 269 | if best.Fitness > e.best.Fitness { 270 | e.best = best 271 | } 272 | } else { 273 | e.best = best 274 | } 275 | } 276 | 277 | // Leave the genomes sorted by their fitness descending 278 | sort.Sort(sort.Reverse(e.population.Genomes)) 279 | return stop, errs.Err() 280 | } 281 | -------------------------------------------------------------------------------- /generator/classic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package generator 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | 32 | "math" 33 | "math/rand" 34 | ) 35 | 36 | type ClassicSettings interface { 37 | // Size of the population 38 | PopulationSize() int 39 | 40 | // Genome used to seed the population 41 | SeedGenome() neat.Genome 42 | Traits() neat.Traits 43 | 44 | // Network definition used if no seed genome is provided 45 | NumInputs() int 46 | NumOutputs() int 47 | OutputActivation() neat.ActivationType 48 | WeightRange() float64 49 | 50 | // Percent of population to be allowed to produce offspring 51 | SurvivalThreshold() float64 52 | 53 | // Probability of producing offspring only by mutation 54 | MutateOnlyProbability() float64 55 | 56 | // Rate at which a mate is chosen from another species 57 | InterspeciesMatingRate() float64 58 | 59 | // Maximum number of generations a stagnant species may exist 60 | MaxStagnation() int 61 | } 62 | 63 | type Classic struct { 64 | ClassicSettings 65 | ctx neat.Context 66 | 67 | cross bool 68 | } 69 | 70 | func (g *Classic) SetContext(x neat.Context) error { 71 | g.ctx = x 72 | return nil 73 | } 74 | 75 | func (g *Classic) SetCrossover(v bool) error { 76 | g.cross = v 77 | return nil 78 | } 79 | 80 | func (g *Classic) Generate(curr neat.Population) (next neat.Population, err error) { 81 | if len(curr.Genomes) == 0 { 82 | return generateFirst(g.ctx, g.ClassicSettings) 83 | } else { 84 | return g.generateNext(curr) 85 | } 86 | } 87 | 88 | // Generates a subsequent population based on the current one 89 | // 90 | // Every species is assigned a potentially different number of offspring in proportion to the sum 91 | // of adjusted fitnesses fi′ of its member organisms. (Stanley, 40) 92 | // 93 | // The lowest performing fraction of each species is eliminated. The parents to produce the next 94 | // generation are chosen randomly among the remaining individuals (uniform distribution with re- 95 | // placement). The highest performing individual in each species, i.e. the species champions, 96 | // carries over from each generation. Otherwise the next generation completely replaces the one 97 | // before. (Stanley, 40) 98 | func (g *Classic) generateNext(curr neat.Population) (next neat.Population, err error) { 99 | 100 | // Update context with current population 101 | for _, h := range []interface{}{g.ctx.Comparer(), g.ctx.Crosser(), g.ctx.Mutator(), g.ctx.Speciater()} { 102 | if ph, ok := h.(neat.Populatable); ok { 103 | if err = ph.SetPopulation(curr); err != nil { 104 | return 105 | } 106 | } 107 | } 108 | 109 | // Advance the population to the next generation 110 | next = neat.Population{ 111 | Generation: curr.Generation + 1, 112 | Species: make([]neat.Species, 0, len(curr.Species)), 113 | Genomes: make([]neat.Genome, 0, g.PopulationSize()), 114 | } 115 | 116 | // Process existing population 117 | pool := createPool(curr) 118 | 119 | // Purge stagnant species unliess it contains the best genome 120 | purgeSpecies(g.ClassicSettings, curr.Species, pool) 121 | 122 | // Calculate offspring counts 123 | cnts := createCounts(g.ClassicSettings, curr.Species, pool) 124 | 125 | // Preserve elites 126 | for i, l := range pool { 127 | if len(l) < 5 { 128 | cnts[i] = cnts[i] + 1 129 | } else { 130 | next.Genomes = append(next.Genomes, l[0]) 131 | } 132 | } 133 | 134 | // Create the offspring 135 | rng := rand.New(rand.NewSource(rand.Int63())) 136 | err = createOffspring(g.ctx, g.ClassicSettings, g.cross, rng, pool, cnts, &next) 137 | if err != nil { 138 | return 139 | } 140 | 141 | // Speciate the genomes 142 | next.Species, err = g.ctx.Speciater().Speciate(curr.Species, next.Genomes) 143 | return 144 | } 145 | 146 | // Every species is assigned a potentially different number of offspring in proportion to the sum 147 | // of adjusted fitnesses fi′ of its member organisms. The net effect of fitness sharing in NEAT 148 | // can be summarized as follows. Let Fk be the average fitness of species k and |P | be the size 149 | // of the population. Let F tot = 􏰇k Fk be the total of all species fitness averages. The number of 150 | // offspring nk allotted to species k is: See figure 3.3 (Stanley, 40) 151 | func createCounts(cfg ClassicSettings, species []neat.Species, pool map[int]Improvements) (cnts map[int]int) { 152 | 153 | // Note the total fitness 154 | var tot float64 155 | for i, l := range pool { 156 | f := l.Improvement() 157 | if species[i].Age < 10 { 158 | f *= 1.2 // Youth boost 159 | } else if species[i].Age > 30 { 160 | f *= 0.2 // Old penalty 161 | } 162 | tot += f 163 | } 164 | 165 | // Calculate the target number of offspring 166 | avail := float64(cfg.PopulationSize() - len(pool)) // preserve room for elite 167 | cnt := 0 168 | cnts = make(map[int]int) 169 | for idx, l := range pool { 170 | f := l.Improvement() 171 | if species[idx].Age < 10 { 172 | f *= 1.2 // Youth boost 173 | } else if species[idx].Age > 30 { 174 | f *= 0.2 // Old penalty 175 | } 176 | 177 | pct := f / tot 178 | tgt := int(math.Ceil(pct * avail)) 179 | cnts[idx] = tgt 180 | cnt += tgt 181 | } 182 | 183 | // Trim back down to overcome rounding in above calculation 184 | for cnt > int(avail) { 185 | for idx, n := range cnts { // Go's range over maps is random. Yay! 186 | if n > 0 { 187 | cnts[idx] = n - 1 188 | cnt -= 1 189 | break 190 | } 191 | } 192 | } 193 | return 194 | } 195 | -------------------------------------------------------------------------------- /generator/common.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package generator 28 | 29 | import ( 30 | "math" 31 | "math/rand" 32 | "sort" 33 | "sync" 34 | 35 | "github.com/rqme/neat" 36 | ) 37 | 38 | // Generates the initial population 39 | func generateFirst(ctx neat.Context, cfg ClassicSettings) (next neat.Population, err error) { 40 | // Create the first generation 41 | next = neat.Population{ 42 | Generation: 0, 43 | Species: make([]neat.Species, 1, 10), 44 | Genomes: make([]neat.Genome, cfg.PopulationSize()), 45 | } 46 | 47 | // Create the genomes 48 | wg := new(sync.WaitGroup) 49 | for i := 0; i < len(next.Genomes); i++ { 50 | wg.Add(1) 51 | go func(i int) { 52 | genome := createSeed(ctx, cfg) 53 | genome.ID = ctx.NextID() 54 | genome.SpeciesIdx = 0 55 | next.Genomes[i] = genome 56 | wg.Done() 57 | }(i) 58 | } 59 | wg.Wait() 60 | 61 | // Create the initial species 62 | next.Species[0] = neat.Species{Example: next.Genomes[0]} 63 | 64 | return 65 | } 66 | 67 | // Creates the pool of potential parents, grouped by species' index. The list of genomes is also 68 | // sorted by fitness in decending order for future operations 69 | func createPool(curr neat.Population) (pool map[int]Improvements) { 70 | pool = make(map[int]Improvements, len(curr.Species)) 71 | for _, genome := range curr.Genomes { 72 | pool[genome.SpeciesIdx] = append(pool[genome.SpeciesIdx], neat.CopyGenome(genome)) 73 | } 74 | for idx, list := range pool { 75 | sort.Sort(sort.Reverse(list)) 76 | pool[idx] = list 77 | } 78 | return 79 | } 80 | 81 | // Removes stagnant species from the pool of possible parents. Allow the species with the most fit 82 | // genome to continue past stagnation. 83 | // 84 | // TODO: Add setting so that user can control whether species with best is removed if stagnant for too long 85 | func purgeSpecies(cfg ClassicSettings, species []neat.Species, pool map[int]Improvements) { 86 | 87 | // Update the species' adjusted fitness and stagnation level and note the best 88 | max := -1.0 89 | best := -1 90 | remove := make([]int, 0, len(species)) 91 | for i, s := range species { 92 | 93 | // Update stagnation and fitness 94 | l := pool[i] 95 | f := l.Improvement() 96 | if f <= s.Improvement { 97 | species[i].Stagnation += 1 98 | } else { 99 | species[i].Stagnation = 0 100 | species[i].Improvement = f 101 | } 102 | 103 | // Plan to remove stagnant species 104 | if species[i].Stagnation > cfg.MaxStagnation() { 105 | remove = append(remove, i) 106 | } else { 107 | // Trim species to just most fit members 108 | cnt := int(math.Max(1.0, float64(len(l))*cfg.SurvivalThreshold())) 109 | pool[i] = l[:cnt] 110 | } 111 | 112 | // Use the same loop to note the species with the best 113 | // Should this be Improvement instead of Fitness? 114 | if l[0].Fitness > max { 115 | max = l[0].Fitness 116 | best = i 117 | } 118 | } 119 | 120 | // Remove any stagnant species 121 | for _, idx := range remove { 122 | if idx != best { 123 | delete(pool, idx) 124 | } 125 | } 126 | } 127 | 128 | func createOffspring(ctx neat.Context, cfg ClassicSettings, cross bool, rng *rand.Rand, pool map[int]Improvements, cnts map[int]int, next *neat.Population) (err error) { 129 | var child neat.Genome 130 | for idx, cnt := range cnts { 131 | l := pool[idx] 132 | for i := 0; i < cnt; i++ { 133 | p1, p2 := pickParents(cfg, cross, rng, l, pool) 134 | if p1.ID == p2.ID { 135 | child = neat.CopyGenome(p1) 136 | } else { 137 | child, err = ctx.Crosser().Cross(p1, p2) 138 | if err != nil { 139 | return 140 | } 141 | } 142 | child.ID = ctx.NextID() 143 | child.Birth = next.Generation 144 | err = ctx.Mutator().Mutate(&child) 145 | next.Genomes = append(next.Genomes, child) 146 | } 147 | } 148 | return 149 | } 150 | 151 | func pickParents(cfg ClassicSettings, cross bool, rng *rand.Rand, species Improvements, pool map[int]Improvements) (p1, p2 neat.Genome) { 152 | 153 | // Parent 1 comes from the species 154 | i := rng.Intn(len(species)) 155 | p1 = species[i] 156 | 157 | if !cross || rng.Float64() < cfg.MutateOnlyProbability() { // Offspring is mutate only -- comes from one parent 158 | p2 = p1 159 | } else { 160 | if rng.Float64() < cfg.InterspeciesMatingRate() { // Offspring could come from any species 161 | for _, l := range pool { 162 | species = l 163 | } 164 | } 165 | i = rng.Intn(len(species)) 166 | p2 = species[i] 167 | } 168 | 169 | return 170 | } 171 | 172 | type Improvements []neat.Genome 173 | 174 | func (f Improvements) Len() int { return len(f) } 175 | func (f Improvements) Less(i, j int) bool { 176 | if f[i].Improvement == f[j].Improvement { 177 | return f[i].Complexity() < f[j].Complexity() 178 | } else { 179 | return f[i].Improvement < f[j].Improvement 180 | } 181 | } 182 | func (f Improvements) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 183 | 184 | // Returns the average fitness for the list 185 | // 186 | // As the reproduction mechanism, NEAT uses explicit fitness sharing (Goldberg and Richard- son 187 | // 1987), where organisms in the same species must share the fitness of their niche. Thus, a 188 | // species cannot afford to become too big even if many of its organisms perform well. Therefore, 189 | // any one species is unlikely to take over the entire population, which is crucial for speciated 190 | // evolution to support a variety of topologies. The adjusted fitness fi′ for organism i is 191 | // calculated according to its distance δ from every other organism j in the population: 192 | // See figure 3.2 193 | // The sharing function sh is set to 0 when distance δ(i,j) is above the threshold δt; otherwise, 194 | // sh(δ(i, j)) is set to 1 (Spears 1995). Thus, 􏰇nj=1 sh(δ(i, j)) reduces to the number of 195 | // organisms in the same species as organism i. This reduction is natural since species are already 196 | // clustered by compatibility using the threshold δt. (Stanley, 39-40) 197 | // 198 | // NOTE: This works out to just taking the average of the improvements 199 | func (f Improvements) Improvement() float64 { 200 | if len(f) == 0 { 201 | return 0 202 | } else { 203 | var sum float64 204 | for _, genome := range f { 205 | sum += genome.Improvement 206 | } 207 | return sum / float64(len(f)) 208 | } 209 | } 210 | 211 | type SpeciesList []*neat.Species 212 | 213 | func (f SpeciesList) Len() int { return len(f) } 214 | func (f SpeciesList) Less(i, j int) bool { return f[i].Improvement < f[j].Improvement } 215 | func (f SpeciesList) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 216 | -------------------------------------------------------------------------------- /generator/realtime.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package generator 28 | 29 | import ( 30 | "math" 31 | "math/rand" 32 | 33 | "github.com/rqme/neat" 34 | ) 35 | 36 | type RealTimeSettings interface { 37 | ClassicSettings 38 | IneligiblePercent() float64 // Fraction of the population ineligible for replacement 39 | MinimumTimeAlive() int // Minimum number of ticks before considered for replacement 40 | } 41 | 42 | type RealTime struct { 43 | RealTimeSettings 44 | ctx neat.Context 45 | 46 | tick int 47 | replace int 48 | cross bool 49 | } 50 | 51 | func (g *RealTime) SetContext(x neat.Context) error { 52 | g.ctx = x 53 | return nil 54 | } 55 | 56 | func (g *RealTime) SetCrossover(v bool) error { 57 | g.cross = v 58 | return nil 59 | } 60 | 61 | func (g *RealTime) Generate(curr neat.Population) (next neat.Population, err error) { 62 | if len(curr.Genomes) == 0 { 63 | next, err = generateFirst(g.ctx, g.RealTimeSettings) 64 | } else { 65 | next, err = g.generateNext(curr) 66 | } 67 | return 68 | } 69 | 70 | func (g *RealTime) generateNext(curr neat.Population) (next neat.Population, err error) { 71 | 72 | // Increment the ticks 73 | g.tick += 1 74 | 75 | // Determine ticks between replacement 76 | m := g.MinimumTimeAlive() 77 | n := int(float64(m) / (float64(g.PopulationSize()) * g.IneligiblePercent())) 78 | 79 | // No replacement this time 80 | if g.tick%n != 0 { 81 | next = curr 82 | return 83 | } 84 | 85 | // Create the pool 86 | pool := createPool(curr) 87 | 88 | // 1. Remove the agent with the worst adjusted fitness from the population assuming one has beeen 89 | // alive sufficiently long so it has been properley evaluated. 90 | if !g.removeWorst(pool, n, m) { 91 | next = curr 92 | return 93 | } 94 | // 2. Re-estimate F for all species. 95 | ftot := g.reestimate(pool) 96 | purgeSpecies(g.RealTimeSettings, curr.Species, pool) 97 | 98 | // 3. Choose a parent species to create the new offspring 99 | rng := rand.New(rand.NewSource(rand.Int63())) 100 | cnts := make(map[int]int, 1) 101 | sidx := g.pickSpecies(pool, ftot, rng) 102 | cnts[sidx] = 1 103 | 104 | next.Generation = curr.Generation + 1 105 | next.Genomes = make([]neat.Genome, 0, len(curr.Genomes)) 106 | if err = createOffspring(g.ctx, g.RealTimeSettings, g.cross, rng, pool, cnts, &next); err != nil { 107 | return 108 | } 109 | 110 | // 4. Adjust compatibility theshold Ct dynamically and reassign all agents to species 111 | for _, list := range pool { 112 | next.Genomes = append(next.Genomes, list...) 113 | } 114 | next.Species, err = g.ctx.Speciater().Speciate(curr.Species, next.Genomes) 115 | 116 | // 5. Place the new agent in the world 117 | return 118 | } 119 | 120 | // Seciont 3.1.1 Step 1: Removing the worst agent (Stanley, p.3) 121 | func (g *RealTime) removeWorst(pool map[int]Improvements, n, m int) bool { 122 | var worst float64 = math.Inf(1) 123 | var wg, ws int 124 | ws = -1 125 | for i, list := range pool { 126 | for j := len(list) - 1; j >= 0; j-- { 127 | adj := list[j].Improvement / float64(len(list)) 128 | if list[j].Birth*n > m && adj < worst { 129 | worst = adj 130 | ws = i 131 | wg = j 132 | break 133 | } 134 | } 135 | } 136 | if ws == -1 { // Entire population is too young to be replaced 137 | return false 138 | } 139 | list := pool[ws] 140 | list = append(list[:wg], list[wg+1:]...) 141 | pool[ws] = list 142 | return true 143 | } 144 | 145 | // Section 3.1.2 Step 2: Re-estimagting F (Stanley, p.4) 146 | func (g *RealTime) reestimate(pool map[int]Improvements) float64 { 147 | ftot := 0.0 148 | for _, list := range pool { 149 | ftot += list.Improvement() 150 | } 151 | return ftot 152 | } 153 | 154 | // Section 3.1.3 Step 3: Choosing the parent species (Stanley, p.4) 155 | func (g *RealTime) pickSpecies(pool map[int]Improvements, ftot float64, rng *rand.Rand) int { 156 | ftgt := rng.Float64() * ftot 157 | fsum := 0.0 158 | for i, list := range pool { 159 | fsum += list.Improvement() 160 | if fsum >= ftgt { 161 | return i 162 | } 163 | } 164 | return -1 // Shouldn't get here 165 | } 166 | -------------------------------------------------------------------------------- /generator/seed.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package generator 28 | 29 | import ( 30 | "math/rand" 31 | 32 | "github.com/rqme/neat" 33 | ) 34 | 35 | // Returns a genome build from the parameters 36 | func createSeed(ctx neat.Context, cfg ClassicSettings) (adam neat.Genome) { 37 | // Create the genome 38 | inputs := cfg.NumInputs() 39 | outputs := cfg.NumOutputs() 40 | adam = neat.Genome{ 41 | Nodes: make(map[int]neat.Node, 1+inputs+outputs), 42 | } 43 | nodes := make([]neat.Node, len(adam.Nodes)) 44 | node := neat.Node{NeuronType: neat.Bias, ActivationType: neat.Direct, X: 0, Y: 0} 45 | node.Innovation = ctx.Innovation(neat.NodeInnovation, node.Key()) 46 | adam.Nodes[node.Innovation] = node 47 | nodes = append(nodes, node) 48 | for i := 0; i < inputs; i++ { 49 | node = neat.Node{NeuronType: neat.Input, ActivationType: neat.Direct, X: float64(i+1) / float64(inputs), Y: 0} 50 | node.Innovation = ctx.Innovation(neat.NodeInnovation, node.Key()) 51 | adam.Nodes[node.Innovation] = node 52 | nodes = append(nodes, node) 53 | } 54 | x := 0.5 55 | for i := 0; i < outputs; i++ { 56 | if outputs > 1 { 57 | x = float64(i) / float64(outputs-1) 58 | } 59 | node = neat.Node{NeuronType: neat.Output, ActivationType: cfg.OutputActivation(), X: x, Y: 1} 60 | node.Innovation = ctx.Innovation(neat.NodeInnovation, node.Key()) 61 | adam.Nodes[node.Innovation] = node 62 | nodes = append(nodes, node) 63 | } 64 | 65 | rng := rand.New(rand.NewSource(rand.Int63())) 66 | adam.Conns = make(map[int]neat.Connection, (1+inputs)*outputs) 67 | for i := 0; i < 1+inputs; i++ { 68 | for j := 0; j < outputs; j++ { 69 | w := (rng.Float64()*2.0 - 1.0) * cfg.WeightRange() 70 | conn := neat.Connection{Source: nodes[i].Innovation, Target: nodes[j+1+inputs].Innovation, Enabled: true, Weight: w} 71 | conn.Innovation = ctx.Innovation(neat.ConnInnovation, conn.Key()) 72 | adam.Conns[conn.Innovation] = conn 73 | } 74 | } 75 | 76 | ts := cfg.Traits() 77 | adam.Traits = make([]float64, len(ts)) 78 | for i, trait := range ts { 79 | adam.Traits[i] = rng.Float64()*(trait.Max-trait.Min) + trait.Min // TODO: Get setting values from configuration 80 | } 81 | return adam 82 | } 83 | -------------------------------------------------------------------------------- /genome.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package neat 28 | 29 | import ( 30 | "bytes" 31 | "fmt" 32 | "sort" 33 | ) 34 | 35 | // An encoded solution 36 | type Genome struct { 37 | ID int // Identifier for this genome 38 | SpeciesIdx int // Index of species in the population 39 | Nodes Nodes // Neuron definitions 40 | Conns Connections // Synapse definitions 41 | Traits []float64 // Trait values 42 | Fitness float64 // Fitness of genome as it relates to the problem itself 43 | Improvement float64 // Fitness of genome as it relates to the improvement of the population 44 | Birth int // Generation during which this genome was born 45 | } 46 | 47 | func (g Genome) Complexity() int { return len(g.Nodes) + len(g.Conns) } 48 | 49 | func (g Genome) String() string { 50 | b := bytes.NewBufferString(fmt.Sprintf("Genome %d Species %d Fitness %f", g.ID, g.SpeciesIdx, g.Fitness)) 51 | nodes, conns := g.GenesByInnovation() 52 | b.WriteString("\n\tNodes:") 53 | for i, n := range nodes { 54 | b.WriteString(fmt.Sprintf("\n\t\t%d %s", i, n.String())) 55 | } 56 | b.WriteString("\n\tConnections:") 57 | for i, c := range conns { 58 | b.WriteString(fmt.Sprintf("\n\t\t%d %s", i, c.String())) 59 | } 60 | b.WriteString("\n\tTraits:") 61 | for i, t := range g.Traits { 62 | b.WriteString(fmt.Sprintf("\n\t\t%d %f", i, t)) 63 | } 64 | return b.String() 65 | } 66 | 67 | // Returns the genome's genes by their markers 68 | func (g Genome) GenesByInnovation() ([]Node, []Connection) { 69 | 70 | // Sort the node genes 71 | n := &sortNodesByInnovation{} 72 | n.nodes = make([]Node, 0, len(g.Nodes)) 73 | for _, ng := range g.Nodes { 74 | n.nodes = append(n.nodes, ng) 75 | } 76 | sort.Sort(n) 77 | 78 | // Sort the conn genes 79 | c := &sortConnsByInnovation{} 80 | c.conns = make([]Connection, 0, len(g.Conns)) 81 | for _, cg := range g.Conns { 82 | c.conns = append(c.conns, cg) 83 | } 84 | sort.Sort(c) 85 | 86 | // Return the collections 87 | return n.nodes, c.conns 88 | } 89 | 90 | // Returns the genome's genes by their keys 91 | func (g Genome) GenesByPosition() ([]Node, []Connection) { 92 | 93 | // Sort the node genes 94 | n := &sortNodesByKey{} 95 | n.nodes = make([]Node, 0, len(g.Nodes)) 96 | for _, ng := range g.Nodes { 97 | n.nodes = append(n.nodes, ng) 98 | } 99 | sort.Sort(n) 100 | 101 | // Sort the conn genes 102 | c := &sortConnsByKey{} 103 | c.nodeMap = g.Nodes 104 | c.conns = make([]Connection, 0, len(g.Conns)) 105 | for _, cg := range g.Conns { 106 | c.conns = append(c.conns, cg) 107 | } 108 | sort.Sort(c) 109 | 110 | // Return the collections 111 | return n.nodes, c.conns 112 | } 113 | 114 | type Genomes []Genome 115 | 116 | func (gs Genomes) Len() int { return len(gs) } 117 | func (gs Genomes) Less(i, j int) bool { return gs[i].Fitness < gs[j].Fitness } 118 | func (gs Genomes) Swap(i, j int) { gs[i], gs[j] = gs[j], gs[i] } 119 | 120 | // Returns a copy of the genome 121 | func CopyGenome(g1 Genome) (g2 Genome) { 122 | g2.ID = g1.ID 123 | g2.SpeciesIdx = g1.SpeciesIdx 124 | g2.Fitness = g1.Fitness 125 | g2.Improvement = g1.Improvement 126 | g2.Conns = make(map[int]Connection, len(g1.Conns)) 127 | for k, v := range g1.Conns { 128 | g2.Conns[k] = v 129 | } 130 | g2.Nodes = make(map[int]Node, len(g1.Nodes)) 131 | for k, v := range g1.Nodes { 132 | g2.Nodes[k] = v 133 | } 134 | g2.Traits = make([]float64, len(g1.Traits)) 135 | copy(g2.Traits, g1.Traits) 136 | return 137 | } 138 | -------------------------------------------------------------------------------- /helpers.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package neat 28 | 29 | // Helper which provides access to archive storage 30 | type Archiver interface { 31 | // Archives the current configuration and state to a more permanent medium 32 | Archive(Context) error 33 | } 34 | 35 | // Helper to compare two genomes similarity 36 | type Comparer interface { 37 | // Returns the compatibility (similarity) between two genomes 38 | Compare(g1, g2 Genome) (float64, error) 39 | } 40 | 41 | // Helper to produce new genome 42 | type Crosser interface { 43 | // Returns new genome that is a cross between two genomes 44 | Cross(g1, g2 Genome) (Genome, error) 45 | } 46 | 47 | // Helper to decode a genome into a phenome 48 | type Decoder interface { 49 | // Returns a decoded version of the genome 50 | Decode(Genome) (Phenome, error) 51 | } 52 | 53 | type Evaluator interface { 54 | // Evaluates a phenome for the problem. Returns the result. 55 | Evaluate(Phenome) Result 56 | } 57 | 58 | // A helper to generate populations 59 | type Generator interface { 60 | // Generates a subsequent population based on the current one 61 | Generate(curr Population) (next Population, err error) 62 | } 63 | 64 | // Helper to mutate a genome 65 | type Mutator interface { 66 | // Mutates a genome 67 | Mutate(*Genome) error 68 | } 69 | 70 | // Helper to restore an experiment 71 | type Restorer interface { 72 | // Restores the configuration and/or the state of a previous experiment 73 | Restore(Context) error 74 | } 75 | 76 | // Helper to search the problem's solution space 77 | type Searcher interface { 78 | // Searches the problem's solution space using the phenomes 79 | Search([]Phenome) ([]Result, error) 80 | } 81 | 82 | // Helper to assign genomes to an exist or new species 83 | type Speciater interface { 84 | // Assigns the genomes to a species. Returns new collection of species. 85 | Speciate(curr []Species, genomes []Genome) (next []Species, err error) 86 | } 87 | 88 | // Helper to visualize a population 89 | type Visualizer interface { 90 | // Creates visual(s) of the population 91 | Visualize(Population) error 92 | } 93 | -------------------------------------------------------------------------------- /innovation.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package neat 28 | 29 | type InnoType byte 30 | 31 | const ( 32 | NodeInnovation InnoType = iota + 1 33 | ConnInnovation 34 | ) 35 | 36 | type InnoKey [2]float64 37 | -------------------------------------------------------------------------------- /mutator/activation.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package mutator 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | 32 | "math/rand" 33 | ) 34 | 35 | type ActivationSettings interface { 36 | MutateActivationProbability() float64 // Probability that the node's activation will be mutated 37 | } 38 | 39 | type Activation struct { 40 | ActivationSettings 41 | } 42 | 43 | // Mutates a genome's weights 44 | func (m Activation) Mutate(g *neat.Genome) error { 45 | rng := rand.New(rand.NewSource(rand.Int63())) 46 | for k, node := range g.Nodes { 47 | if node.NeuronType == neat.Hidden { 48 | if rng.Float64() < m.MutateActivationProbability() { 49 | node.ActivationType = neat.Activations[rng.Intn(len(neat.Activations))] 50 | g.Nodes[k] = node 51 | } 52 | } 53 | } 54 | return nil 55 | } 56 | -------------------------------------------------------------------------------- /mutator/classic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package mutator 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | ) 32 | 33 | type Classic struct { 34 | Complexify 35 | Weight 36 | Trait 37 | } 38 | 39 | func New(cs ComplexifySettings, ws WeightSettings, ts TraitSettings) *Classic { 40 | return &Classic{ 41 | Complexify: Complexify{ComplexifySettings: cs}, 42 | Weight: Weight{WeightSettings: ws}, 43 | Trait: Trait{TraitSettings: ts}, 44 | } 45 | } 46 | 47 | func (m *Classic) SetContext(x neat.Context) error { 48 | m.ctx = x 49 | return m.Complexify.SetContext(x) 50 | } 51 | 52 | func (c Classic) Mutate(g *neat.Genome) error { 53 | old := g.Complexity() 54 | if err := c.Complexify.Mutate(g); err != nil { 55 | return err 56 | } 57 | if g.Complexity() == old { 58 | if err := c.Weight.Mutate(g); err != nil { 59 | return err 60 | } 61 | if err := c.Trait.Mutate(g); err != nil { 62 | return err 63 | } 64 | } 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /mutator/complete.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package mutator 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | ) 32 | 33 | type Complete struct { 34 | Phased 35 | Weight 36 | Trait 37 | Activation 38 | } 39 | 40 | func NewComplete(ps PhasedSettings, cs ComplexifySettings, ns PruningSettings, ws WeightSettings, ts TraitSettings, as ActivationSettings) *Complete { 41 | return &Complete{ 42 | Phased: *NewPhased(ps, cs, ns), 43 | Weight: Weight{WeightSettings: ws}, 44 | Trait: Trait{TraitSettings: ts}, 45 | Activation: Activation{ActivationSettings: as}, 46 | } 47 | } 48 | 49 | func (m *Complete) SetContext(x neat.Context) error { 50 | m.ctx = x 51 | return m.Complexify.SetContext(x) 52 | } 53 | 54 | // Sets the population 55 | func (m *Complete) SetPopulation(p neat.Population) error { 56 | return m.Phased.SetPopulation(p) 57 | } 58 | 59 | func (c Complete) Mutate(g *neat.Genome) error { 60 | old := g.Complexity() 61 | if err := c.Phased.Mutate(g); err != nil { 62 | return err 63 | } 64 | if g.Complexity() == old { 65 | if err := c.Weight.Mutate(g); err != nil { 66 | return err 67 | } 68 | if err := c.Trait.Mutate(g); err != nil { 69 | return err 70 | } 71 | if err := c.Activation.Mutate(g); err != nil { 72 | return err 73 | } 74 | } 75 | return nil 76 | } 77 | -------------------------------------------------------------------------------- /mutator/complexify.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package mutator 28 | 29 | import ( 30 | "math/rand" 31 | 32 | "github.com/rqme/neat" 33 | ) 34 | 35 | // Complexifying mutation settings 36 | type ComplexifySettings interface { 37 | WeightRange() float64 // The mutation range of the weight. If x, range is [-x,x] 38 | AddNodeProbability() float64 // Probablity a node will be added to the genome 39 | AddConnProbability() float64 // Probability a connection will be added to the genome 40 | HiddenActivation() neat.ActivationType // Activation type to assign to new nodes 41 | } 42 | 43 | type Complexify struct { 44 | ComplexifySettings 45 | ctx neat.Context 46 | } 47 | 48 | func (m *Complexify) SetContext(x neat.Context) error { 49 | m.ctx = x 50 | return nil 51 | } 52 | 53 | // Mutates a genome's weights 54 | func (m *Complexify) Mutate(g *neat.Genome) error { 55 | rng := rand.New(rand.NewSource(rand.Int63())) 56 | if rng.Float64() < m.AddNodeProbability() { 57 | m.addNode(rng, g) 58 | } else if rng.Float64() < m.AddConnProbability() { 59 | m.addConn(rng, g) 60 | } 61 | return nil 62 | } 63 | 64 | // Adds a new node to the genome 65 | // 66 | // In the add node mutation, an existing connection is split and the new node placed where the old 67 | // connection used to be. The old connection is disabled and two new connections are added to the 68 | // genome. The connection between the first node in the chain and the new node is given a weight 69 | // of one, and the connection between the new node and the last node in the chain is given the 70 | // same weight as the connection being split. Splitting the connection in this way introduces a 71 | // nonlinearity (i.e. sigmoid function) where there was none before. Because the new node is 72 | // immediately integrated into the network, its effect on fitness can be evaluated right away. 73 | // Preexisting network structure is not destroyed and performs the same function, while the new 74 | // structure provides an opportunity to elaborate on the original behaviors. (Stanley, 35) 75 | func (m *Complexify) addNode(rng *rand.Rand, g *neat.Genome) { 76 | 77 | // Pick a connection to split 78 | var inno int 79 | var c0 neat.Connection 80 | found := false 81 | for k, conn := range g.Conns { 82 | c0 = conn 83 | inno = k 84 | 85 | // Ensure resultant node doesn't already exist 86 | found = true 87 | src := g.Nodes[c0.Source] 88 | tgt := g.Nodes[c0.Target] 89 | x := (src.X + tgt.X) / 2.0 90 | y := (src.Y + tgt.Y) / 2.0 91 | for _, node := range g.Nodes { 92 | if node.X == x && node.Y == y { 93 | found = false 94 | break 95 | } 96 | } 97 | if found { 98 | break 99 | } 100 | } 101 | if !found { 102 | return 103 | } 104 | c0.Enabled = false 105 | g.Conns[inno] = c0 106 | 107 | // Add the new node 108 | src := g.Nodes[c0.Source] 109 | tgt := g.Nodes[c0.Target] 110 | n0 := neat.Node{NeuronType: neat.Hidden, ActivationType: m.HiddenActivation(), X: (src.X + tgt.X) / 2.0, Y: (src.Y + tgt.Y) / 2.0} 111 | n0.Innovation = m.ctx.Innovation(neat.NodeInnovation, n0.Key()) 112 | g.Nodes[n0.Innovation] = n0 113 | 114 | // Add the new connections 115 | c1 := neat.Connection{Source: src.Innovation, Target: n0.Innovation, Enabled: true, Weight: 1.0} 116 | c1.Innovation = m.ctx.Innovation(neat.ConnInnovation, c1.Key()) 117 | g.Conns[c1.Innovation] = c1 118 | 119 | c2 := neat.Connection{Source: n0.Innovation, Target: tgt.Innovation, Enabled: true, Weight: c0.Weight} 120 | c2.Innovation = m.ctx.Innovation(neat.ConnInnovation, c2.Key()) 121 | g.Conns[c2.Innovation] = c2 122 | } 123 | 124 | // Adds a new connection to the genome 125 | // 126 | // In the add connection mutation, a single new connection gene is added connecting two previously 127 | // unconnected nodes. (Stanley, 35) 128 | func (m *Complexify) addConn(rng *rand.Rand, g *neat.Genome) { 129 | 130 | // Identify two unconnected nodes 131 | conns := make(map[int]neat.Connection) 132 | c := 0 133 | for _, src := range g.Nodes { 134 | for _, tgt := range g.Nodes { 135 | if src.Y >= tgt.Y { 136 | continue // do not allow recurrent 137 | } 138 | found := false 139 | for _, c2 := range g.Conns { 140 | if c2.Source == src.Innovation && c2.Target == tgt.Innovation { 141 | found = true 142 | break 143 | } 144 | } 145 | if !found { 146 | conns[c] = neat.Connection{Source: src.Innovation, Target: tgt.Innovation} 147 | c += 1 148 | } 149 | } 150 | } 151 | 152 | // Go's range over maps is random, so take the first, if any, availble connection 153 | for _, conn := range conns { 154 | conn.Enabled = true 155 | conn.Weight = (rng.Float64()*2.0 - 1.0) * m.WeightRange() 156 | conn.Innovation = m.ctx.Innovation(neat.ConnInnovation, conn.Key()) 157 | g.Conns[conn.Innovation] = conn 158 | break 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /mutator/phased.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package mutator 28 | 29 | import ( 30 | "github.com/montanaflynn/stats" 31 | "github.com/rqme/neat" 32 | ) 33 | 34 | // Phased mutatation settings 35 | type PhasedSettings interface { 36 | PruningPhaseThreshold() float64 37 | MaxMPCAge() int 38 | MaxImprovementAge() int 39 | ImprovementType() neat.FitnessType 40 | } 41 | 42 | // Phased Searching 43 | // As an alternative to blended searching I propose the use of 'phased' searching, so called because 44 | // the NEAT search switches between a complexifying phase and a simplifying(or pruning) phase. 45 | // 46 | // Phased searching works as follows: 47 | // 48 | // 1) Before the search starts proper, calculate a threshold at which the prune phase will begin. This 49 | // threshold is the current mean population complexity (MPC)** plus a specified pruning phase 50 | // threshold value which typically might be between 30-100 depending on the type of experiment. 51 | // 2) The search begins in complexifying mode and continues as traditional NEAT until the prune phase 52 | // threshold is reached. 53 | // 3) The search now enters a prune phase. The prune phase is almost algorithmically identical to the 54 | // complexifying phase, and the normal process of selecting genomes for reproduction, generating 55 | // offspring, monitoring species compatibility, etc. all takes place. The difference is that the 56 | // additive mutations are disabled and subtractive ones enabled in their place. In addition only 57 | // asexual reproduction (with mutation) is allowed. Crossover is disabled because this can allow 58 | // genes to propagate through a population, thus increasing complexity. 59 | // 4) During each generation of the pruning phase a reading of the MPC is taken, this will normally 60 | // be seen to fall as functionally redundant structures are removed from the population. As pruning 61 | // progresses the MPC will eventually reach a floor level when no more redundant structure remains 62 | // in the population to be removed. Therefore once MPC has not fallen for some number of generations 63 | // (this is configurable, between 10-50 works well), we can reset the next pruning phase's threshold 64 | // to be the current MPC floor level + the pruning phase threshold parameter and switch into a 65 | // complexifying phase. The whole process then begins again at (2). 66 | // 67 | // One small modification was made to the above process, and that is to not enter prune phase unless 68 | // the population fitness has not risen for some specified number of generations. Therefore if the 69 | // complexity has risen past the pruning phase threshold but the population fitness is still rising 70 | // then we hold off the pruning phase until the fitness stops rising. 71 | // 72 | // from Colin Green (http://sharpneat.sourceforge.net/phasedsearch.html) 73 | type Phased struct { 74 | PhasedSettings 75 | ctx neat.Context 76 | 77 | // Inner mutators 78 | Complexify 79 | Pruning 80 | 81 | // internal state 82 | isPruning bool 83 | pruneThresh float64 84 | targetMPC, fitness float64 85 | ageMPC, ageImprovement int 86 | minMPC, lastImprovement float64 87 | } 88 | 89 | func NewPhased(ps PhasedSettings, cs ComplexifySettings, ns PruningSettings) *Phased { 90 | return &Phased{ 91 | PhasedSettings: ps, 92 | Complexify: Complexify{ComplexifySettings: cs}, 93 | Pruning: Pruning{PruningSettings: ns}, 94 | } 95 | } 96 | 97 | func (m *Phased) SetContext(x neat.Context) error { 98 | m.ctx = x 99 | return m.Complexify.SetContext(x) 100 | } 101 | 102 | // Mutates the Genome by through complexifiying or pruning depending on current phase 103 | func (m *Phased) Mutate(g *neat.Genome) (err error) { 104 | if m.isPruning { 105 | err = m.Pruning.Mutate(g) 106 | } else { 107 | err = m.Complexify.Mutate(g) 108 | } 109 | return 110 | } 111 | 112 | // Updates the statistics of the population and determines if a phase switch is required. 113 | func (m *Phased) SetPopulation(p neat.Population) error { 114 | 115 | // Calculate the tnew MPC and fitness 116 | var n float64 117 | fit := make([]float64, len(p.Genomes)) 118 | for i, g := range p.Genomes { 119 | n += float64(g.Complexity()) 120 | fit[i] = g.Improvement 121 | } 122 | mpc := n / float64(len(p.Genomes)) 123 | //neat.DBG("mpc %f fit %f", mpc, fit) 124 | if mpc < m.minMPC { // Looking for a drop in MPC 125 | m.ageMPC = 0 126 | m.minMPC = mpc 127 | } else { 128 | m.ageMPC += 1 129 | } 130 | 131 | var f float64 132 | if m.ImprovementType() == neat.Absolute { 133 | f, _ = stats.Max(fit) 134 | } else { 135 | f, _ = stats.VarP(fit) 136 | } 137 | 138 | // Looking for a continued increase in fitness (Absolute) or an uptick in variance (RelativeImprovement) 139 | if f > m.lastImprovement { 140 | m.ageImprovement = 0 141 | m.lastImprovement = f 142 | } else { 143 | m.ageImprovement += 1 144 | } 145 | 146 | // First run, just set the initial threshold and return 147 | if m.targetMPC == 0 { 148 | m.isPruning = false 149 | m.targetMPC = mpc + m.PruningPhaseThreshold() 150 | return nil 151 | } 152 | 153 | // Check for a phase change 154 | if m.isPruning { 155 | if m.ageMPC > m.MaxMPCAge() { 156 | m.isPruning = false 157 | m.targetMPC = mpc + m.PruningPhaseThreshold() 158 | m.ageImprovement = 0 159 | m.lastImprovement = 0 160 | } 161 | } else { 162 | if mpc >= m.targetMPC && m.ageImprovement > m.MaxImprovementAge() { 163 | m.isPruning = true 164 | m.ageMPC = 0 165 | m.minMPC = mpc 166 | } 167 | } 168 | 169 | // Toggle crossover as necessary 170 | if crs, ok := m.ctx.(neat.Crossoverable); ok { 171 | crs.SetCrossover(!m.isPruning) 172 | } 173 | return nil 174 | } 175 | -------------------------------------------------------------------------------- /mutator/pruning.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package mutator 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | 32 | "math/rand" 33 | ) 34 | 35 | // Pruning mutation settings 36 | type PruningSettings interface { 37 | DelNodeProbability() float64 // Probablity a node will be removed to the genome 38 | DelConnProbability() float64 // Probability a connection will be removed to the genome 39 | } 40 | 41 | type Pruning struct { 42 | PruningSettings 43 | } 44 | 45 | // Mutates a genome's structure by selectiving removing nodes and connections 46 | func (m Pruning) Mutate(g *neat.Genome) error { 47 | rng := rand.New(rand.NewSource(rand.Int63())) 48 | if rng.Float64() < m.DelNodeProbability() { 49 | m.delNode(rng, g) 50 | } else if rng.Float64() < m.DelConnProbability() { 51 | m.delConn(rng, g) 52 | } 53 | return nil 54 | } 55 | 56 | // Removes a hidden node from the genome 57 | // 58 | // Neuron deletion is slightly more complex. The deletion algorithm attempts to replace neurons with 59 | // connections to maintain any circuits a neuron may have participated in, in further generations 60 | // those connections themselves will be open to deletion. This approach provides NEAT with the ability 61 | // to delete whole structures, not just connections. 62 | // 63 | // Because we replace connected neurons with connections we must be careful which neurons we delete. 64 | // Any neuron with only incoming or only outgoing connections is at a dead-end of a circuit and can 65 | // therefore be safely deleted with all of it's connections. However, a neuron with multiple incoming 66 | // and multiple outgoing connections will require a large number of connections to substitute for the 67 | // loss of the neuron - we must fully connect all of the original neuron's source neurons with its 68 | // target neurons, this could be done but may actually be detrimental since the functionality 69 | // represented by the neuron is now distributed over a number of connections, and this cannot easily 70 | // be reversed. Because of this, such neurons are omitted from the process of selecting neurons for 71 | // deletion. 72 | // 73 | // Neurons with only one incoming or one outgoing connection can be replaced with however many 74 | // connections were on the other side of the neuron, therefore these are candidates for deletion. 75 | func (m *Pruning) delNode(rng *rand.Rand, g *neat.Genome) { 76 | 77 | type check struct { 78 | AsSource []neat.Connection 79 | AsTarget []neat.Connection 80 | } 81 | 82 | // Build a map of available nodes to delete 83 | var chk check 84 | var node neat.Node 85 | avail := make(map[neat.Node]check) 86 | for _, node = range g.Nodes { 87 | if node.NeuronType == neat.Hidden { 88 | chk = check{make([]neat.Connection, 0, 5), make([]neat.Connection, 0, 5)} 89 | for _, conn := range g.Conns { 90 | if conn.Source == node.Innovation { 91 | chk.AsSource = append(chk.AsSource, conn) 92 | } else if conn.Target == node.Innovation { 93 | chk.AsTarget = append(chk.AsTarget, conn) 94 | } 95 | } 96 | if len(chk.AsSource) <= 1 || len(chk.AsTarget) <= 1 { 97 | avail[node] = chk 98 | } 99 | } 100 | } 101 | if len(avail) == 0 { 102 | return // there are nodes available to delete 103 | } 104 | 105 | // Pick a node to delete 106 | for node, chk = range avail { 107 | break 108 | } 109 | 110 | // Remove dead-end connections 111 | if len(chk.AsSource) == 0 { 112 | for _, conn := range chk.AsTarget { 113 | delete(g.Conns, conn.Innovation) 114 | } 115 | } else if len(chk.AsTarget) == 0 { 116 | for _, conn := range chk.AsSource { 117 | delete(g.Conns, conn.Innovation) 118 | } 119 | } else { 120 | // Bypass this node in all the connections. Only one of the slices will have more than 1 connection 121 | for _, sc := range chk.AsSource { 122 | for _, tc := range chk.AsTarget { 123 | sc.Target = tc.Target 124 | tc.Source = sc.Source 125 | g.Conns[sc.Innovation] = sc 126 | g.Conns[tc.Innovation] = tc 127 | } 128 | } 129 | } 130 | } 131 | 132 | // Removes a connection from the genome 133 | // 134 | // Connection deletion is very simply the deletion of a randomly selected connection, all connections 135 | // are considered to be available for deletion. When a connection is deleted the neurons that were at 136 | // each end of the connection are tested to check if they are no longer connected to by other 137 | // connections, if this is the case then the stranded neuron is also deleted. Note that a more thorough 138 | // cleanup routine could be invoked at this point that cleans up any dead-end structures that could not 139 | // possibly be functional, but this can become complex and so we leave NEAT to eliminate such structures 140 | // naturally. 141 | func (m *Pruning) delConn(rng *rand.Rand, g *neat.Genome) { 142 | 143 | // Pick a connection at random 144 | var conn neat.Connection 145 | for _, conn = range g.Conns { 146 | break 147 | } 148 | 149 | // Remove the connection from the genome 150 | delete(g.Conns, conn.Innovation) 151 | 152 | // Look for orphaned nodes 153 | var node neat.Node 154 | var found bool 155 | for i := 0; i < 2; i++ { 156 | found = false 157 | if i == 0 { 158 | node = g.Nodes[conn.Source] 159 | } else { 160 | node = g.Nodes[conn.Target] 161 | } 162 | if node.NeuronType != neat.Hidden { 163 | continue 164 | } 165 | 166 | // Check for another connection using this node gene 167 | for _, conn2 := range g.Conns { 168 | if conn2.Source == node.Innovation || conn2.Target == node.Innovation { 169 | found = true 170 | break 171 | } 172 | } 173 | if !found { 174 | delete(g.Nodes, node.Innovation) 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /mutator/trait.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package mutator 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | 32 | "math/rand" 33 | ) 34 | 35 | type TraitSettings interface { 36 | Traits() neat.Traits // Repository of traits 37 | MutateTraitProbability() float64 // Probability that the trait will be mutated 38 | ReplaceTraitProbability() float64 // Probability that the trait will be replaced 39 | MutateSettingProbability() float64 // Probability that the setting will be mutated 40 | ReplaceSettingProbability() float64 // Probability that the setting will be replaced 41 | } 42 | 43 | type Trait struct { 44 | TraitSettings 45 | } 46 | 47 | // Mutates a genome's traits 48 | func (m Trait) Mutate(g *neat.Genome) error { 49 | rng := rand.New(rand.NewSource(rand.Int63())) 50 | ts := m.Traits() 51 | for i, _ := range g.Traits { 52 | t := ts[i] 53 | if t.IsSetting { 54 | if rng.Float64() < m.MutateTraitProbability() { 55 | if rng.Float64() < m.ReplaceTraitProbability() { 56 | m.replaceTrait(rng, t, &g.Traits[i]) 57 | } else { 58 | m.mutateTrait(rng, t, &g.Traits[i]) 59 | } 60 | } 61 | } else { 62 | if rng.Float64() < m.MutateTraitProbability() { 63 | if rng.Float64() < m.ReplaceTraitProbability() { 64 | m.replaceTrait(rng, t, &g.Traits[i]) 65 | } else { 66 | m.mutateTrait(rng, t, &g.Traits[i]) 67 | } 68 | } 69 | } 70 | } 71 | return nil 72 | } 73 | 74 | func (m *Trait) replaceTrait(rng *rand.Rand, t neat.Trait, v *float64) { 75 | *v = (rng.Float64() * (t.Max - t.Min)) + t.Min 76 | } 77 | 78 | func (m *Trait) mutateTrait(rng *rand.Rand, t neat.Trait, v *float64) { 79 | tv := *v 80 | tv += rng.NormFloat64() 81 | if tv < t.Min { 82 | tv = t.Min 83 | } else if tv > t.Max { 84 | tv = t.Max 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /mutator/weight.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package mutator 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | 32 | "math/rand" 33 | ) 34 | 35 | type WeightSettings interface { 36 | WeightRange() float64 // The mutation range of the weight. If x, range is [-x,x] 37 | MutateWeightProbability() float64 // Probability that the weight will be mutated 38 | ReplaceWeightProbability() float64 // Probability that the weight will be replaced 39 | } 40 | 41 | type Weight struct { 42 | WeightSettings 43 | } 44 | 45 | // Mutates a genome's weights 46 | func (m Weight) Mutate(g *neat.Genome) error { 47 | rng := rand.New(rand.NewSource(rand.Int63())) 48 | for k, conn := range g.Conns { 49 | if rng.Float64() < m.MutateWeightProbability() { 50 | if rng.Float64() < m.ReplaceWeightProbability() { 51 | m.replaceWeight(rng, &conn) 52 | } else { 53 | m.mutateWeight(rng, &conn) 54 | } 55 | g.Conns[k] = conn 56 | } 57 | } 58 | return nil 59 | } 60 | 61 | // Returns a modified weight 62 | func (m Weight) mutateWeight(rng *rand.Rand, c *neat.Connection) { 63 | c.Weight += rng.NormFloat64() 64 | /* 65 | if w < -m.WeightRange*2 { 66 | w = -m.WeightRange * 2 67 | } else if w > m.WeightRange*2 { 68 | w = m.WeightRange * 2 69 | } 70 | */ 71 | } 72 | 73 | // Returns a new weight 74 | func (m Weight) replaceWeight(rng *rand.Rand, c *neat.Connection) { 75 | c.Weight = (rng.Float64()*2.0 - 1.0) * m.WeightRange() 76 | } 77 | -------------------------------------------------------------------------------- /network.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package neat 28 | 29 | import "math" 30 | 31 | // Represents a neural network 32 | type Network interface { 33 | 34 | // Activates the neural network using the inputs. Returns the ouput values. 35 | Activate(inputs []float64) (outputs []float64, err error) 36 | } 37 | 38 | type NeuronType byte 39 | 40 | const ( 41 | Bias NeuronType = iota + 1 // 1 42 | Input // 2 43 | Hidden // 3 44 | Output // 4 45 | ) 46 | 47 | func (n NeuronType) String() string { 48 | switch n { 49 | case Bias: 50 | return "Bias" 51 | case Input: 52 | return "Input" 53 | case Hidden: 54 | return "Hidden" 55 | case Output: 56 | return "Output" 57 | default: 58 | return "Unknown NeuronType" 59 | } 60 | } 61 | 62 | type ActivationType byte 63 | 64 | const ( 65 | Direct ActivationType = iota + 1 // 1 66 | SteependSigmoid // 2 67 | Sigmoid // 3 68 | Tanh // 4 69 | InverseAbs // 5 70 | ) 71 | 72 | var ( 73 | Activations []ActivationType = []ActivationType{SteependSigmoid, Sigmoid, Tanh, InverseAbs} 74 | ) 75 | 76 | func (a ActivationType) String() string { 77 | switch a { 78 | case Direct: 79 | return "Direct" 80 | case SteependSigmoid: 81 | return "Steepend Sigmoid" 82 | case Sigmoid: 83 | return "Sigmoid" 84 | case Tanh: 85 | return "Tanh" 86 | case InverseAbs: 87 | return "Inverse ABS" 88 | default: 89 | return "Unknown ActivationType" 90 | } 91 | } 92 | 93 | func (a ActivationType) Range() (float64, float64) { 94 | switch a { 95 | case Direct: 96 | return math.Inf(-1), math.Inf(1) 97 | case SteependSigmoid: 98 | return 0, 1.0 99 | case Sigmoid: 100 | return 0, 1.0 101 | case Tanh: 102 | return -1.0, 1.0 103 | case InverseAbs: 104 | return -1.0, 1.0 105 | default: 106 | return math.NaN(), math.NaN() 107 | } 108 | } 109 | 110 | func DirectActivation(x float64) float64 { return x } 111 | func SigmoidActivation(x float64) float64 { return 1.0 / (1.0 + exp1(-x)) } 112 | func SteependSigmoidActivation(x float64) float64 { return 1.0 / (1.0 + exp1(-4.9*x)) } 113 | func TanhActivation(x float64) float64 { return math.Tanh(0.9 * x) } 114 | func InverseAbsActivation(x float64) float64 { return x / (1.0 + math.Abs(x)) } 115 | 116 | // Speed up over math.Exp by using less precision 117 | // https://codingforspeed.com/using-faster-exponential-approximation/ 118 | func exp1(x float64) float64 { 119 | x = 1.0 + x/256.0 120 | x *= x 121 | x *= x 122 | x *= x 123 | x *= x 124 | x *= x 125 | x *= x 126 | x *= x 127 | x *= x 128 | return x 129 | } 130 | 131 | func exp2(x float64) float64 { 132 | x = 1.0 + x/1024 133 | x *= x 134 | x *= x 135 | x *= x 136 | x *= x 137 | x *= x 138 | x *= x 139 | x *= x 140 | x *= x 141 | x *= x 142 | x *= x 143 | return x 144 | } 145 | -------------------------------------------------------------------------------- /network/classic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015 Brian Hummer (brian@redq.me), All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted 5 | provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this list of conditions 8 | and the following disclaimer. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the documentation and/or other 10 | materials provided with the distribution. Neither the name of the nor the names of its 11 | contributors may be used to endorse or promote products derived from this software without 12 | specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 13 | CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 14 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 15 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 16 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 17 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 18 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 19 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 20 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 21 | */ 22 | 23 | package network 24 | 25 | import ( 26 | "bytes" 27 | "fmt" 28 | 29 | "github.com/rqme/neat" 30 | ) 31 | 32 | type Neuron struct { 33 | neat.NeuronType 34 | neat.ActivationType 35 | X, Y float64 // Hint at where neuron might be positioned in a 2D representation 36 | } 37 | 38 | type Neurons []Neuron 39 | 40 | type Synapse struct { 41 | Source, Target int // Indexes of source and target neurons 42 | Weight float64 43 | } 44 | 45 | type Synapses []Synapse 46 | 47 | func (s Synapses) Len() int { return len(s) } 48 | func (s Synapses) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 49 | func (s Synapses) Less(i, j int) bool { 50 | if s[i].Source == s[j].Source { 51 | return s[i].Target < s[j].Target 52 | } else { 53 | return s[i].Source == s[j].Source 54 | } 55 | } 56 | 57 | type Activation func(float64) float64 58 | 59 | type Classic struct { 60 | 61 | // Structure 62 | Neurons Neurons 63 | Synapses Synapses 64 | 65 | // Internal state 66 | biases, inputs, hiddens, outputs int 67 | funcs []Activation 68 | } 69 | 70 | func New(neurons Neurons, synapses Synapses) (net *Classic, err error) { 71 | 72 | // Begin a new network 73 | net = &Classic{Neurons: neurons, Synapses: synapses} 74 | 75 | // Create the internal state and check for errors 76 | oo := false // out-of-order check 77 | l := neat.Bias // last neuron type created 78 | net.funcs = make([]Activation, len(neurons)) 79 | for i, ng := range neurons { 80 | switch ng.NeuronType { 81 | case neat.Bias: 82 | net.biases += 1 83 | oo = oo || l > neat.Bias 84 | case neat.Input: 85 | net.inputs += 1 86 | oo = oo || l > neat.Input 87 | case neat.Hidden: 88 | net.hiddens += 1 89 | oo = oo || l > neat.Hidden 90 | case neat.Output: 91 | net.outputs += 1 92 | oo = oo || l > neat.Output 93 | } 94 | l = ng.NeuronType 95 | switch ng.ActivationType { 96 | case neat.Direct: 97 | net.funcs[i] = neat.DirectActivation 98 | case neat.Sigmoid: 99 | net.funcs[i] = neat.SigmoidActivation 100 | case neat.SteependSigmoid: 101 | net.funcs[i] = neat.SteependSigmoidActivation 102 | case neat.Tanh: 103 | net.funcs[i] = neat.TanhActivation 104 | case neat.InverseAbs: 105 | net.funcs[i] = neat.InverseAbsActivation 106 | default: 107 | err = fmt.Errorf("network.classic.New - Unknown ActivationType %v", byte(ng.ActivationType)) 108 | break 109 | } 110 | } 111 | if oo { 112 | err = fmt.Errorf("network.classic.New - Neurons are out of order") 113 | return 114 | } 115 | 116 | // Ensure we have inputs and outputs 117 | if net.inputs == 0 { 118 | err = fmt.Errorf("network.classic.New - Network must have at least 1 input neuron") 119 | return 120 | } 121 | if net.outputs == 0 { 122 | err = fmt.Errorf("network.classic.New - Network must have at least 1 output neuron") 123 | return 124 | } 125 | 126 | // Ensure the synapses map to neurons and count the sources 127 | cnt := len(net.Neurons) 128 | for _, s := range net.Synapses { 129 | if s.Source > cnt || s.Target > cnt { 130 | err = fmt.Errorf("network.classic.New - Synapses do not map to defined neurons") 131 | return 132 | } 133 | } 134 | 135 | // Sort the synapses for efficient processing 136 | //sort.Sort(net.Synapses) 137 | return 138 | } 139 | 140 | func (n Classic) String() string { 141 | b := bytes.NewBufferString("Network is \n") 142 | b.WriteString("\tNeurons:\n") 143 | for i, neuron := range n.Neurons { 144 | b.WriteString(fmt.Sprintf("\t [%d] Type: %v Activation: %v Position: [%f, %f]\n", i, neuron.NeuronType, neuron.ActivationType, neuron.X, neuron.Y)) 145 | } 146 | b.WriteString("\tSynapses:\n") 147 | for i, synapse := range n.Synapses { 148 | b.WriteString(fmt.Sprintf("\t [%d] Source: %d Target: %d Weight: %f\n", i, synapse.Source, synapse.Target, synapse.Weight)) 149 | } 150 | return b.String() 151 | } 152 | 153 | func (n Classic) Activate(inputs []float64) (outputs []float64, err error) { 154 | 155 | // Create the data structure 156 | val := make([]float64, len(n.Neurons)) 157 | 158 | // Set the biases 159 | for i := 0; i < n.biases; i++ { 160 | val[i] = 1.0 161 | } 162 | 163 | // Copy inputs into the network 164 | if len(inputs) > n.inputs { 165 | err = fmt.Errorf("network.classic.Activate - There are more input values (%d) than input neurons (%d)", len(inputs), n.inputs) 166 | return 167 | } 168 | copy(val[n.biases:], inputs) 169 | 170 | // Iterate the network synapse by synapse 171 | for _, s := range n.Synapses { 172 | v := n.funcs[s.Source](val[s.Source]) 173 | val[s.Target] += v * s.Weight 174 | } 175 | 176 | // Return the output values 177 | offset := len(val) - n.outputs 178 | outputs = make([]float64, n.outputs) 179 | for i := 0; i < len(outputs); i++ { 180 | v := n.funcs[i+offset](val[i+offset]) 181 | outputs[i] = v 182 | } 183 | return 184 | } 185 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package neat 28 | 29 | import ( 30 | "bytes" 31 | "encoding/json" 32 | "fmt" 33 | "sort" 34 | ) 35 | 36 | // Definition of a neuron 37 | type Node struct { 38 | Innovation int 39 | X, Y float64 40 | NeuronType 41 | ActivationType 42 | } 43 | 44 | func (n Node) Key() (k InnoKey) { 45 | k[0] = n.X 46 | k[1] = n.Y 47 | return 48 | } 49 | 50 | func (n Node) String() string { 51 | return fmt.Sprintf("Node %d at [%f, %f] Neuron %v Activation %v", n.Innovation, n.X, n.Y, n.NeuronType, n.ActivationType) 52 | } 53 | 54 | // Nodes is a map of nodes by innovation number 55 | type Nodes map[int]Node 56 | 57 | // nodeToSlice converts a nodes map into a slice for encoding 58 | func (nm Nodes) nodesToSlice() []Node { 59 | 60 | // Maps with non-string keys cannot be encoded. Transfer to a slice to handle this 61 | items := &sortNodesByInnovation{make([]Node, 0, len(nm))} 62 | for _, s := range nm { 63 | items.nodes = append(items.nodes, s) 64 | } 65 | sort.Sort(items) 66 | return items.nodes 67 | } 68 | 69 | // nodesFromSlice converts a slice into a nodes map for decoding 70 | func (nm *Nodes) nodesFromSlice(items []Node) { 71 | if *nm == nil { 72 | *nm = make(map[int]Node) 73 | } 74 | m := *nm 75 | for _, s := range items { 76 | m[s.Innovation] = s 77 | } 78 | } 79 | 80 | // MarshalJSON marshals the nodes map into JSON 81 | func (nm Nodes) MarshalJSON() ([]byte, error) { 82 | items := nm.nodesToSlice() 83 | buf := new(bytes.Buffer) 84 | enc := json.NewEncoder(buf) 85 | err := enc.Encode(items) 86 | return buf.Bytes(), err 87 | } 88 | 89 | // UnmarshalJSON unmarshals JSON into a nodes map 90 | func (nm *Nodes) UnmarshalJSON(data []byte) error { 91 | buf := bytes.NewBuffer(data) 92 | dec := json.NewDecoder(buf) 93 | var items []Node 94 | err := dec.Decode(&items) 95 | if err == nil { 96 | nm.nodesFromSlice(items) 97 | } 98 | return err 99 | } 100 | 101 | type sortNodesByKey struct { 102 | nodes []Node 103 | } 104 | 105 | func (g *sortNodesByKey) Len() int { return len(g.nodes) } 106 | func (g *sortNodesByKey) Less(i, j int) bool { 107 | if g.nodes[i].Y == g.nodes[j].Y { 108 | return g.nodes[i].X < g.nodes[j].X 109 | } else { 110 | return g.nodes[i].Y < g.nodes[j].Y 111 | } 112 | } 113 | func (g *sortNodesByKey) Swap(i, j int) { g.nodes[i], g.nodes[j] = g.nodes[j], g.nodes[i] } 114 | 115 | type sortNodesByInnovation struct { 116 | nodes []Node 117 | } 118 | 119 | func (g *sortNodesByInnovation) Len() int { return len(g.nodes) } 120 | func (g *sortNodesByInnovation) Less(i, j int) bool { 121 | return g.nodes[i].Innovation < g.nodes[j].Innovation 122 | } 123 | func (g *sortNodesByInnovation) Swap(i, j int) { g.nodes[i], g.nodes[j] = g.nodes[j], g.nodes[i] } 124 | -------------------------------------------------------------------------------- /result/classic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package result 28 | 29 | type Classic struct { 30 | id int 31 | fitness float64 32 | err error 33 | stop bool 34 | } 35 | 36 | func New(id int, fitness float64, err error, stop bool) Classic { 37 | return Classic{id, fitness, err, stop} 38 | } 39 | 40 | // Returns the ID of the phenome 41 | func (r Classic) ID() int { return r.id } 42 | 43 | // Returns the fitness of the phenome for the problem 44 | func (r Classic) Fitness() float64 { return r.fitness } 45 | 46 | // Returns the error, if any, occuring while evaluating the phenome. 47 | func (r Classic) Err() error { return r.err } 48 | 49 | // Returns true if the stop condition was met 50 | func (r Classic) Stop() bool { return r.stop } 51 | -------------------------------------------------------------------------------- /result/novelty.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package result 28 | 29 | type Novelty struct { 30 | Classic 31 | behavior []float64 32 | novelty float64 33 | } 34 | 35 | func NewNovelty(id int, fitness float64, err error, stop bool, behavior []float64) *Novelty { 36 | return &Novelty{Classic: New(id, fitness, err, stop), behavior: behavior} 37 | } 38 | 39 | func (r Novelty) Behavior() []float64 { return r.behavior } 40 | 41 | func (r Novelty) Novelty() float64 { return r.novelty } 42 | 43 | func (r *Novelty) SetNovelty(v float64) { r.novelty = v } 44 | 45 | func (r Novelty) Improvement() float64 { return r.novelty } 46 | -------------------------------------------------------------------------------- /searcher/concurrent.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package searcher 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | ) 32 | 33 | type Concurrent struct { 34 | ctx neat.Context 35 | } 36 | 37 | func (s *Concurrent) SetContext(x neat.Context) error { 38 | s.ctx = x 39 | return nil 40 | } 41 | 42 | // Searches the phenomes concurrently and returns the results 43 | func (s Concurrent) Search(phenomes []neat.Phenome) ([]neat.Result, error) { 44 | r := make(chan neat.Result) 45 | for _, p := range phenomes { 46 | go func(p neat.Phenome) { 47 | r <- s.ctx.Evaluator().Evaluate(p) 48 | }(p) 49 | } 50 | results := make([]neat.Result, len(phenomes)) 51 | for i := 0; i < len(phenomes); i++ { 52 | results[i] = <-r 53 | } 54 | return results, nil 55 | } 56 | -------------------------------------------------------------------------------- /searcher/novelty.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package searcher 28 | 29 | import ( 30 | "fmt" 31 | "math" 32 | "sort" 33 | "sync" 34 | 35 | "github.com/rqme/neat" 36 | "github.com/rqme/neat/result" 37 | ) 38 | 39 | type distrec struct { 40 | id int 41 | dist float64 42 | } 43 | 44 | type distrecs []distrec 45 | 46 | func (d distrecs) Len() int { return len(d) } 47 | func (d distrecs) Less(i, j int) bool { return d[i].dist < d[j].dist } 48 | func (d distrecs) Swap(i, j int) { d[i], d[j] = d[j], d[i] } 49 | 50 | type BehaviorRecord struct { 51 | ID int 52 | Behavior []float64 53 | } 54 | 55 | type BehaviorRecords []BehaviorRecord 56 | 57 | func (b BehaviorRecords) Len() int { return len(b) } 58 | func (b BehaviorRecords) Less(i, j int) bool { return b[i].ID < b[j].ID } 59 | func (b BehaviorRecords) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 60 | 61 | type NoveltySettings interface { 62 | NoveltyEvalArchive() bool // True = evaluate archive during each search 63 | NoveltyArchiveThreshold() float64 // Threshold at which to admit phenome into archive 64 | NumNearestNeighbors() int // K-nearest neighbors 65 | } 66 | type Novelty struct { 67 | NoveltySettings 68 | 69 | neat.Searcher 70 | archive neat.Phenomes 71 | behaviors BehaviorRecords 72 | sync.Mutex 73 | } 74 | 75 | func (s *Novelty) SetContext(x neat.Context) error { 76 | x.State()["novelty-behaviors"] = &s.behaviors 77 | if cx, ok := s.Searcher.(neat.Contextable); ok { 78 | return cx.SetContext(x) 79 | } 80 | return nil 81 | } 82 | 83 | // Searches the phenomes one by one and returns the results 84 | func (s Novelty) Search(phenomes []neat.Phenome) ([]neat.Result, error) { 85 | 86 | // Re-evaluate archive phenomes if necessary 87 | var bs BehaviorRecords = make([]BehaviorRecord, 0, len(phenomes)+len(s.behaviors)) // behaviors 88 | if !s.NoveltyEvalArchive() && len(s.behaviors) > 0 { 89 | bs = append(bs, s.behaviors...) 90 | } 91 | 92 | // Execute the search using the inner searcher 93 | var rs []neat.Result 94 | var err error 95 | if s.NoveltyEvalArchive() && len(s.archive) > 0 { 96 | rs, err = s.Searcher.Search(append(phenomes, s.archive...)) 97 | } else { 98 | rs, err = s.Searcher.Search(phenomes) 99 | } 100 | 101 | if err != nil { 102 | err = fmt.Errorf("Error running search: %v", err) 103 | return nil, err 104 | } 105 | 106 | // Ensure result includes behavior and can SetNovelty 107 | for _, r := range rs { 108 | if br, ok := r.(neat.Behaviorable); ok { 109 | bs = append(bs, BehaviorRecord{ID: r.ID(), Behavior: br.Behavior()}) 110 | } else { 111 | err = fmt.Errorf("Result from evalutor did not implement Behaviorable") 112 | return nil, err 113 | } 114 | } 115 | sort.Sort(bs) 116 | 117 | // Calculate novelty 118 | wg := new(sync.WaitGroup) 119 | nrs := make([]neat.Result, len(rs)) 120 | for i, r := range rs { 121 | wg.Add(1) 122 | go func(idx int, r neat.Result) { 123 | 124 | // Find this phenome's record 125 | id := r.ID() 126 | i := sort.Search(len(bs), func(i int) bool { return bs[i].ID >= id }) 127 | bsi := bs[i] 128 | 129 | // Create a list of all the other records 130 | o := append(bs[:i], bs[i+1:]...) 131 | 132 | // Create the distance records 133 | var ds distrecs = make([]distrec, len(o)) 134 | for j := 0; j < len(o); j++ { 135 | ds[j].id = o[j].ID 136 | ds[j].dist = calcDist(bsi.Behavior, o[j].Behavior) 137 | } 138 | 139 | // Sort the records and take the K nearest neighbors 140 | sort.Sort(ds) 141 | sum := 0.0 142 | k := s.NumNearestNeighbors() 143 | for j := 0; j < k; j++ { 144 | sum += 1.0 / float64(k) * ds[j].dist 145 | } 146 | 147 | // Set the novelty 148 | var ok bool 149 | var nr *result.Novelty 150 | if nr, ok = r.(*result.Novelty); !ok { 151 | nr = result.NewNovelty(id, r.Fitness(), r.Err(), r.Stop(), bsi.Behavior) 152 | } 153 | nr.SetNovelty(sum) 154 | nrs[idx] = nr 155 | if sum > s.NoveltyArchiveThreshold() { 156 | s.Lock() 157 | //s.Archive = append(s.Archive, ) 158 | s.behaviors = append(s.behaviors, bsi) 159 | s.Unlock() 160 | } 161 | wg.Done() 162 | }(i, r) 163 | } 164 | wg.Wait() 165 | 166 | // Determine the novelty of each phenome against the 167 | // Process the results, setting novelty, replace Fitness with novelty 168 | return nrs, err 169 | } 170 | 171 | func calcDist(a, b []float64) float64 { 172 | sum := 0.0 173 | for i := 0; i < len(a); i++ { 174 | sum += ((a[i] - b[i]) * (a[i] - b[i])) 175 | } 176 | return math.Sqrt(sum) 177 | } 178 | -------------------------------------------------------------------------------- /searcher/serial.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package searcher 28 | 29 | import "github.com/rqme/neat" 30 | 31 | type Serial struct { 32 | ctx neat.Context 33 | } 34 | 35 | func (s *Serial) SetContext(x neat.Context) error { 36 | s.ctx = x 37 | return nil 38 | } 39 | 40 | // Searches the phenomes one by one and returns the results 41 | func (s Serial) Search(phenomes []neat.Phenome) ([]neat.Result, error) { 42 | results := make([]neat.Result, len(phenomes)) 43 | for i, phenome := range phenomes { 44 | results[i] = s.ctx.Evaluator().Evaluate(phenome) 45 | } 46 | return results, nil 47 | } 48 | -------------------------------------------------------------------------------- /speciater/classic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package speciater 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | ) 32 | 33 | type ClassicSettings interface { 34 | CompatibilityThreshold() float64 // Threshold above which two genomes are not compatible 35 | } 36 | 37 | type Classic struct { 38 | ClassicSettings 39 | ctx neat.Context 40 | } 41 | 42 | func (s *Classic) SetContext(x neat.Context) error { 43 | s.ctx = x 44 | return nil 45 | } 46 | 47 | // Assigns the genomes to a species. Returns new collection of species. 48 | // 49 | // Throughout evolution, NEAT maintains a list of species numbered in the order they ap- peared. In 50 | // the first generation, since there are no preexisting species, NEAT begins by creating species 1 51 | // and placing the first genome into that species. All other genomes are placed into species as 52 | // follows: A random member of each existing species is chosen as its permanent representative. 53 | // Genomes are tested one at a time; if a genome’s distance to the representative of any existing 54 | // species is less than δt, a compatibility threshold, it is placed into this species. Otherwise, 55 | // if it is not compatible with any existing species, a new species is created and given a new 56 | // number. After the first generation, genomes are first compared with species from the previous 57 | // generation so that the same species numbers can be used to identify species throughout the run. 58 | // (Stanley, 39) 59 | // 60 | // TODO: Pick a random reprentative each time instead of permanently recording it with the species 61 | // record 62 | func (s Classic) Speciate(curr []neat.Species, genomes []neat.Genome) (next []neat.Species, err error) { 63 | 64 | // Copy the species to the new set 65 | next = make([]neat.Species, len(curr)) 66 | for i, s := range curr { 67 | next[i] = s 68 | next[i].Age = s.Age + 1 69 | } 70 | 71 | // Iterate the genomes, looking for target species 72 | // TODO: This could be made concurrent if it is slow 73 | var δ float64 74 | cnts := make([]int, len(curr)) 75 | for i, genome := range genomes { 76 | found := false 77 | for j, species := range next { 78 | δ, err = s.ctx.Comparer().Compare(genome, species.Example) 79 | if err != nil { 80 | return 81 | } 82 | if δ < s.CompatibilityThreshold() { 83 | genomes[i].SpeciesIdx = j 84 | cnts[j] += 1 85 | found = true 86 | break 87 | } 88 | } 89 | if !found { 90 | genomes[i].SpeciesIdx = len(next) 91 | cnts = append(cnts, 1) 92 | species := neat.Species{ 93 | Example: neat.CopyGenome(genomes[i]), 94 | } 95 | next = append(next, species) 96 | 97 | } 98 | } 99 | 100 | // Purge unused species 101 | i := 0 102 | for i < len(next) { 103 | if cnts[i] == 0 { 104 | next = append(next[:i], next[i+1:]...) 105 | cnts = append(cnts[:i], cnts[i+1:]...) 106 | for j := 0; j < len(genomes); j++ { 107 | if genomes[j].SpeciesIdx > i { 108 | genomes[j].SpeciesIdx -= 1 109 | } 110 | } 111 | } else { 112 | i += 1 113 | } 114 | } 115 | 116 | return 117 | } 118 | -------------------------------------------------------------------------------- /speciater/dynamic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package speciater 28 | 29 | import ( 30 | "github.com/rqme/neat" 31 | ) 32 | 33 | const ( 34 | CompatibilityThresholdFloor = 0.3 35 | ) 36 | 37 | type DynamicSettings interface { 38 | SetCompatibilityThreshold(float64) // Threshold above which two genomes are not compatible 39 | TargetNumberOfSpecies() int // The desired number of species 40 | CompatibilityModifier() float64 // Amount to change the compatibility threshold for next iteration 41 | } 42 | 43 | type Dynamic struct { 44 | DynamicSettings 45 | Classic 46 | } 47 | 48 | func NewDynamic(ds DynamicSettings, cs ClassicSettings) *Dynamic { 49 | return &Dynamic{ 50 | DynamicSettings: ds, 51 | Classic: Classic{ClassicSettings: cs}, 52 | } 53 | } 54 | 55 | func (s *Dynamic) Speciate(curr []neat.Species, genomes []neat.Genome) (next []neat.Species, err error) { 56 | 57 | // Speciate using the internal speciater 58 | next, err = s.Classic.Speciate(curr, genomes) 59 | if err != nil { 60 | return 61 | } 62 | 63 | // Adjust the compatibily theshold as necessary 64 | ct := s.CompatibilityThreshold() 65 | if len(next) < s.TargetNumberOfSpecies() { 66 | ct -= s.CompatibilityModifier() 67 | if ct < CompatibilityThresholdFloor { 68 | ct = CompatibilityThresholdFloor 69 | } 70 | } else if len(next) > s.TargetNumberOfSpecies() { 71 | ct += s.CompatibilityModifier() 72 | } 73 | s.SetCompatibilityThreshold(ct) 74 | return 75 | } 76 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package neat 28 | 29 | import ( 30 | "fmt" 31 | ) 32 | 33 | // A decoded solution 34 | type Phenome interface { 35 | ID() int // The identify of the underlying genome 36 | Traits() []float64 // Trait values which could be used during evaluation 37 | Network // The decoded genome 38 | } 39 | 40 | type Phenomes []Phenome 41 | 42 | // Population of genomes for a given generation 43 | type Population struct { 44 | Generation int 45 | Species []Species 46 | Genomes Genomes 47 | } 48 | 49 | // The result of an evaluation 50 | type Result interface { 51 | ID() int // Returns the ID of the phenome 52 | Fitness() float64 // Returns the fitness of the phenome for the problem 53 | Err() error // Returns the error, if any, occuring while evaluating the phenome. 54 | Stop() bool // Returns true if the stop condition was met 55 | } 56 | 57 | type Results []Result 58 | 59 | type Species struct { 60 | Age int // Age in terms of generations 61 | Stagnation int // Number of generations since an improvement 62 | Improvement float64 63 | Example Genome 64 | } 65 | 66 | type Trait struct { 67 | Name string 68 | Min, Max float64 69 | IsSetting bool 70 | } 71 | 72 | type Traits []Trait 73 | 74 | func (t Traits) IndexOf(name string) int { 75 | for i, trait := range t { 76 | if trait.Name == name { 77 | return i 78 | } 79 | } 80 | return -1 81 | } 82 | 83 | type FitnessType byte 84 | 85 | const ( 86 | Absolute FitnessType = iota + 1 // 1 87 | Relative // 2 88 | ) 89 | 90 | func (f FitnessType) String() string { 91 | switch f { 92 | case Absolute: 93 | return "Absolute Fitness" 94 | case Relative: 95 | return "Relative Fitness" 96 | default: 97 | return fmt.Sprintf("Unknown FitnessType: %d", f) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /visualizer/null.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | ATTRIBUTIONS and other notes: 28 | * This visualizer is an adaptation of the NeuroEvolution Visualization Toolkit which can be 29 | found at http://sourceforge.net/projects/nevt/ which was released under LGPL v2 license. All 30 | functionality derived from NEVT retains the original copyright. 31 | 32 | * The SVG creation is made easier with SVGo, github.com/ajstarks/svgo, which was released 33 | under the Creative Commons license. 34 | 35 | * The statistics creation takes advantage of the stats library, github.com/montanaflynn/stats 36 | */ 37 | 38 | package visualizer 39 | 40 | import "github.com/rqme/neat" 41 | 42 | // Visualizes the population by creating web pages 43 | type Null struct{} 44 | 45 | // Creates visuals of the population which can be displayed in a browser 46 | func (v Null) Visualize(pop neat.Population) error { 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /x/examples/boxes/boxes-eshyperneat-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExperimentName": "Boxes ESHyperNEAT", 3 | "PopulationSize": 100, 4 | "Iterations": 500, 5 | "NumInputs": 4, 6 | "NumOutputs": 1, 7 | "FitnessType": 0, 8 | 9 | "TargetNumberOfSpecies": 8, 10 | "CompatibilityModifier": 0.3, 11 | "CompatibilityThreshold": 3.0, 12 | 13 | "DisjointCoefficient": 2.0, 14 | "ExcessCoefficient": 2.0, 15 | "WeightCoefficient": 1.0, 16 | 17 | "AddConnProbability": 0.1, 18 | "AddNodeProbability": 0.03, 19 | "EnableProbability": 0.2, 20 | "HiddenActivation": 2, 21 | "MutateActivationProbability": 0.25, 22 | "MutateOnlyProbability": 0.25, 23 | "MutateSettingProbability": 0, 24 | "MutateTraitProbability": 0, 25 | "MutateWeightProbability": 0.9, 26 | "ReplaceSettingProbability": 0, 27 | "ReplaceTraitProbability": 0, 28 | "ReplaceWeightProbability": 0.2, 29 | "WeightRange": 2.5, 30 | 31 | "InterspeciesMatingRate": 0.001, 32 | "MateByAveragingProbability": 0.4, 33 | "MaxStagnation": 15, 34 | "OutputActivation": 4, 35 | "SurvivalThreshold": 0.2, 36 | 37 | "InitialDepth": 3, 38 | "MaxDepth": 3, 39 | "DivisionThreshold": 0.3, 40 | "VarianceThreshold": 0.3, 41 | "BandThreshold": 0.3, 42 | "IterationLevels": 1, 43 | 44 | "ArchivePath": "/tmp/boxes/eshyperneat", 45 | "WebPath": "/tmp/boxes/eshyperneat" 46 | } 47 | -------------------------------------------------------------------------------- /x/examples/boxes/boxes-hyperneat-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExperimentName": "Boxes HyperNEAT", 3 | "PopulationSize": 100, 4 | "Iterations": 500, 5 | "NumInputs": 4, 6 | "NumOutputs": 1, 7 | "FitnessType": 0, 8 | 9 | "TargetNumberOfSpecies": 8, 10 | "CompatibilityModifier": 0.3, 11 | "CompatibilityThreshold": 3.0, 12 | 13 | "DisjointCoefficient": 2.0, 14 | "ExcessCoefficient": 2.0, 15 | "WeightCoefficient": 1.0, 16 | 17 | "AddConnProbability": 0.1, 18 | "AddNodeProbability": 0.03, 19 | "EnableProbability": 0.2, 20 | "HiddenActivation": 2, 21 | "MutateActivationProbability": 0.25, 22 | "MutateOnlyProbability": 0.25, 23 | "MutateSettingProbability": 0, 24 | "MutateTraitProbability": 0, 25 | "MutateWeightProbability": 0.9, 26 | "ReplaceSettingProbability": 0, 27 | "ReplaceTraitProbability": 0, 28 | "ReplaceWeightProbability": 0.2, 29 | "WeightRange": 2.5, 30 | 31 | "InterspeciesMatingRate": 0.001, 32 | "MateByAveragingProbability": 0.4, 33 | "MaxStagnation": 15, 34 | "OutputActivation": 4, 35 | "SurvivalThreshold": 0.2, 36 | 37 | "ArchivePath": "/tmp/boxes/hyperneat", 38 | "WebPath": "/tmp/boxes/hyperneat" 39 | } 40 | -------------------------------------------------------------------------------- /x/examples/boxes/boxes-neat-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExperimentName": "Boxes NEAT", 3 | "PopulationSize": 100, 4 | "Iterations": 500, 5 | "NumInputs": 121, 6 | "NumOutputs": 11, 7 | "FitnessType": 0, 8 | 9 | "TargetNumberOfSpecies": 8, 10 | "CompatibilityModifier": 0.3, 11 | "CompatibilityThreshold": 3.0, 12 | 13 | "DisjointCoefficient": 2.0, 14 | "ExcessCoefficient": 2.0, 15 | "WeightCoefficient": 1.0, 16 | 17 | "AddConnProbability": 0, 18 | "AddNodeProbability": 0, 19 | "EnableProbability": 0.2, 20 | "HiddenActivation": 2, 21 | "MutateActivationProbability": 0.25, 22 | "MutateOnlyProbability": 0.25, 23 | "MutateSettingProbability": 0, 24 | "MutateTraitProbability": 0, 25 | "MutateWeightProbability": 0.9, 26 | "ReplaceSettingProbability": 0, 27 | "ReplaceTraitProbability": 0, 28 | "ReplaceWeightProbability": 0.2, 29 | "WeightRange": 2.5, 30 | 31 | "InterspeciesMatingRate": 0.001, 32 | "MateByAveragingProbability": 0.4, 33 | "MaxStagnation": 15, 34 | "OutputActivation": 2, 35 | "SurvivalThreshold": 0.2, 36 | 37 | "ArchivePath": "/tmp/boxes/neat", 38 | "WebPath": "/tmp/boxes/neat" 39 | } 40 | -------------------------------------------------------------------------------- /x/examples/boxes/boxes-pneat-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExperimentName": "Boxes P-NEAT", 3 | "PopulationSize": 100, 4 | "Iterations": 500, 5 | "NumInputs": 121, 6 | "NumOutputs": 11, 7 | "FitnessType": 0, 8 | 9 | "TargetNumberOfSpecies": 8, 10 | "CompatibilityModifier": 0.3, 11 | "CompatibilityThreshold": 3.0, 12 | 13 | "DisjointCoefficient": 2.0, 14 | "ExcessCoefficient": 2.0, 15 | "WeightCoefficient": 1.0, 16 | 17 | "AddConnProbability": 0, 18 | "AddNodeProbability": 0, 19 | "EnableProbability": 0.2, 20 | "HiddenActivation": 2, 21 | "MutateActivationProbability": 0.25, 22 | "MutateOnlyProbability": 0.25, 23 | "MutateSettingProbability": 0, 24 | "MutateTraitProbability": 0, 25 | "MutateWeightProbability": 0.9, 26 | "ReplaceSettingProbability": 0, 27 | "ReplaceTraitProbability": 0, 28 | "ReplaceWeightProbability": 0.2, 29 | "WeightRange": 2.5, 30 | 31 | "InterspeciesMatingRate": 0.001, 32 | "MateByAveragingProbability": 0.4, 33 | "MaxStagnation": 15, 34 | "OutputActivation": 2, 35 | "SurvivalThreshold": 0.2, 36 | 37 | "ArchivePath": "/tmp/boxes/pneat", 38 | "WebPath": "/tmp/boxes/pneat" 39 | } 40 | -------------------------------------------------------------------------------- /x/examples/boxes/hyperneat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/rqme/neat" 5 | "github.com/rqme/neat/decoder" 6 | ) 7 | 8 | type SettingsWithLayers struct { 9 | decoder.ESHyperNEATSettings 10 | layers []decoder.SubstrateNodes 11 | } 12 | 13 | func (h SettingsWithLayers) SubstrateLayers() []decoder.SubstrateNodes { return h.layers } 14 | 15 | // The solution substrate is configured as a state-space sandwich that includes two sheets: (1) The 16 | // visual field is a two-dimensional array of sensors that are either on or off (i.e. black or 17 | // white); (2) The target field is an equivalent two-dimensional array of outputs that are 18 | // activated at variable intensity between zero and one. (Stanley, p.15) 19 | func newSettingsWithLayers(cfg decoder.ESHyperNEATSettings) (hns SettingsWithLayers) { 20 | hns.ESHyperNEATSettings = cfg 21 | 22 | // Create the substrate layers 23 | r := *Resolution 24 | var ilayer decoder.SubstrateNodes = make([]decoder.SubstrateNode, 0, r*r) 25 | var olayer decoder.SubstrateNodes = make([]decoder.SubstrateNode, 0, r*r) 26 | for x := 0; x < r; x++ { 27 | for y := 0; y < r; y++ { 28 | px := float64(x)/float64(r-1)*2.0 - 1.0 29 | py := float64(y)/float64(r-1)*2.0 - 1.0 30 | ilayer = append(ilayer, decoder.SubstrateNode{Position: []float64{px, py}, NeuronType: neat.Input}) 31 | olayer = append(olayer, decoder.SubstrateNode{Position: []float64{px, py}, NeuronType: neat.Output}) 32 | } 33 | } 34 | hns.layers = []decoder.SubstrateNodes{ilayer, olayer} 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /x/examples/maze/classic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | "log" 31 | "os" 32 | "path" 33 | 34 | svg "github.com/ajstarks/svgo" 35 | "github.com/rqme/neat" 36 | "github.com/rqme/neat/result" 37 | ) 38 | 39 | type Evaluator struct { 40 | Environment 41 | show bool 42 | 43 | useTrial bool 44 | trialNum int 45 | points []Point 46 | } 47 | 48 | func (e *Evaluator) ShowWork(s bool) { 49 | e.show = s 50 | } 51 | 52 | func (e *Evaluator) SetTrial(t int) error { 53 | e.useTrial = true 54 | e.trialNum = t 55 | return nil 56 | } 57 | 58 | func (e *Evaluator) Takedown() error { 59 | // Create maze image with all endpints (cumulative accross all iterations) 60 | var t string 61 | if e.useTrial { 62 | t = fmt.Sprintf("-%d", e.trialNum) 63 | } 64 | env := e.Environment.clone() 65 | return showMaze(path.Join(*WorkPath, fmt.Sprintf("maze%s-end-points.svg", t)), env, nil, e.points) 66 | } 67 | 68 | func (e *Evaluator) Evaluate(p neat.Phenome) (r neat.Result) { 69 | 70 | // Clone the environemnt 71 | var env *Environment 72 | cln := e.Environment.clone() 73 | env = &cln 74 | env.init() 75 | h := &env.Hero 76 | 77 | // Iterate the maze 78 | var err error 79 | paths := make([]Line, 0, *Steps) 80 | stop := false 81 | for i := 0; i < *Steps; i++ { 82 | 83 | // Note the start of the path 84 | a := h.Location 85 | 86 | // Update the hero's location 87 | var outputs []float64 88 | inputs := generateNeuralInputs(*env) 89 | if outputs, err = p.Activate(inputs); err != nil { 90 | break 91 | } 92 | interpretOutputs(env, outputs[0], outputs[1]) 93 | update(env) 94 | b := h.Location 95 | paths = append(paths, Line{A: a, B: b}) 96 | 97 | // Look for solution 98 | stop = distanceToTarget(env) < 5.0 99 | if stop { 100 | break 101 | } 102 | } 103 | f := 300.0 - distanceToTarget(env) // fitness 104 | e.points = append(e.points, h.Location) 105 | b := []float64{h.Location.X, h.Location.Y} // behavior 106 | r = &Result{Classic: result.New(p.ID(), f, err, stop), behavior: b} 107 | 108 | // Output the maze 109 | if e.show { 110 | // Write the file 111 | var t string 112 | if e.useTrial { 113 | t = fmt.Sprintf("-%d", e.trialNum) 114 | } 115 | if err := showMaze(path.Join(*WorkPath, fmt.Sprintf("maze%s-%d.svg", t, p.ID())), *env, paths, nil); err != nil { 116 | log.Println("Could not output maze run:", err) 117 | } 118 | } 119 | return 120 | } 121 | 122 | func showMaze(p string, e Environment, hist []Line, pts []Point) error { 123 | 124 | // Determine image size 125 | var h, w float64 126 | for _, line := range e.Lines { 127 | if line.A.X > w { 128 | w = line.A.X 129 | } 130 | if line.A.Y > h { 131 | h = line.A.Y 132 | } 133 | if line.B.X > w { 134 | w = line.B.X 135 | } 136 | if line.B.Y > h { 137 | h = line.B.Y 138 | } 139 | } 140 | 141 | // Create the image 142 | f, err := os.Create(p) 143 | if err != nil { 144 | return err 145 | } 146 | defer f.Close() 147 | 148 | img := svg.New(f) 149 | img.Start(int(w), int(h)) 150 | defer img.End() 151 | 152 | // Add the maze 153 | if len(hist) > 0 { 154 | img.Circle(int(hist[0].A.X), int(hist[0].A.Y), 4, `fill="green"`) // start 155 | } 156 | img.Circle(int(e.End.X), int(e.End.Y), 4, `fill="red"`) 157 | 158 | for _, line := range e.Lines { 159 | img.Path(fmt.Sprintf("M %f %f L %f %f", line.A.X, line.A.Y, line.B.X, line.B.Y), `stroke-width="1" stroke="black" fill="none"`) 160 | } 161 | 162 | for _, line := range hist { 163 | img.Path(fmt.Sprintf("M %f %f L %f %f", line.A.X, line.A.Y, line.B.X, line.B.Y), `stroke-width="1" stroke="blue" fill="none"`) 164 | 165 | } 166 | 167 | for _, point := range pts { 168 | img.Circle(int(point.X), int(point.Y), 1, `fill="green"`) 169 | } 170 | return nil 171 | } 172 | -------------------------------------------------------------------------------- /x/examples/maze/hard_maze.txt: -------------------------------------------------------------------------------- 1 | 13 2 | 36 184 3 | 0 4 | 31 20 5 | 41 5 3 8 6 | 3 8 4 49 7 | 4 49 57 53 8 | 4 49 7 202 9 | 7 202 195 198 10 | 195 198 186 8 11 | 186 8 39 5 12 | 56 54 56 157 13 | 57 106 158 162 14 | 77 201 108 164 15 | 6 80 33 121 16 | 192 146 87 91 17 | 56 55 133 30 18 | -------------------------------------------------------------------------------- /x/examples/maze/maze-0-end-points.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /x/examples/maze/maze-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExperimentName": "Maze", 3 | "PopulationSize": 250, 4 | "Iterations": 200, 5 | "NumInputs": 10, 6 | "NumOutputs": 2, 7 | "FitnessType": 0, 8 | 9 | "TargetNumberOfSpecies": 15, 10 | "CompatibilityModifier": 0.3, 11 | "CompatibilityThreshold": 3.0, 12 | 13 | "DisjointCoefficient": 1, 14 | "ExcessCoefficient": 1, 15 | "WeightCoefficient": 0.4, 16 | 17 | "AddConnProbability": 0.1, 18 | "AddNodeProbability": 0.005, 19 | "EnableProbability": 0.2, 20 | "HiddenActivation": 2, 21 | "MutateOnlyProbability": 0.25, 22 | "MutateSettingProbability": 0, 23 | "MutateTraitProbability": 0, 24 | "MutateWeightProbability": 0.9, 25 | "ReplaceSettingProbability": 0, 26 | "ReplaceTraitProbability": 0, 27 | "ReplaceWeightProbability": 0.2, 28 | "WeightRange": 2.5, 29 | 30 | "InterspeciesMatingRate": 0.001, 31 | "MateByAveragingProbability": 0.4, 32 | "MaxStagnation": 15, 33 | "OutputActivation": 2, 34 | "SurvivalThreshold": 0.2, 35 | 36 | "ArchivePath": "/tmp/maze", 37 | "WebPath": "/tmp/maze", 38 | 39 | "NoveltyArchiveThreshold": 6.0, 40 | "NumNearestNeighbors": 15 41 | } -------------------------------------------------------------------------------- /x/examples/maze/maze-realtime-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExperimentName": "Maze", 3 | "PopulationSize": 250, 4 | "Iterations": 2000, 5 | "NumInputs": 10, 6 | "NumOutputs": 2, 7 | "FitnessType": 0, 8 | 9 | "TargetNumberOfSpecies": 15, 10 | "CompatibilityModifier": 0.3, 11 | "CompatibilityThreshold": 3.0, 12 | 13 | "DisjointCoefficient": 1, 14 | "ExcessCoefficient": 1, 15 | "WeightCoefficient": 0.4, 16 | 17 | "AddConnProbability": 0.1, 18 | "AddNodeProbability": 0.005, 19 | "EnableProbability": 0.2, 20 | "HiddenActivation": 2, 21 | "MutateOnlyProbability": 0.25, 22 | "MutateSettingProbability": 0, 23 | "MutateTraitProbability": 0, 24 | "MutateWeightProbability": 0.9, 25 | "ReplaceSettingProbability": 0, 26 | "ReplaceTraitProbability": 0, 27 | "ReplaceWeightProbability": 0.2, 28 | "WeightRange": 2.5, 29 | 30 | "InterspeciesMatingRate": 0.001, 31 | "MateByAveragingProbability": 0.4, 32 | "MaxStagnation": 15, 33 | "OutputActivation": 2, 34 | "SurvivalThreshold": 0.2, 35 | "IneligiblePercent": 0.5, 36 | "MinimumTimeAlive": 400, 37 | 38 | "ArchivePath": "/tmp/maze", 39 | "WebPath": "/tmp/maze", 40 | 41 | "NoveltyArchiveThreshold": 6.0, 42 | "NumNearestNeighbors": 15 43 | } -------------------------------------------------------------------------------- /x/examples/maze/medium_maze.txt: -------------------------------------------------------------------------------- 1 | 11 2 | 30 22 3 | 0 4 | 270 100 5 | 293 7 289 130 6 | 289 130 6 134 7 | 6 134 8 5 8 | 8 5 292 7 9 | 241 130 58 65 10 | 114 7 73 42 11 | 130 91 107 46 12 | 196 8 139 51 13 | 219 122 182 63 14 | 267 9 214 63 15 | 271 129 237 88 16 | -------------------------------------------------------------------------------- /x/examples/maze/realtime.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | package main 27 | 28 | import ( 29 | "fmt" 30 | "log" 31 | "path" 32 | 33 | "github.com/rqme/neat" 34 | "github.com/rqme/neat/result" 35 | ) 36 | 37 | type RTEvaluator struct { 38 | Evaluator 39 | History map[int]*Environment 40 | } 41 | 42 | func (e *RTEvaluator) SetPhenomes(ps neat.Phenomes) error { 43 | if len(e.History) == 0 { 44 | e.History = make(map[int]*Environment, len(ps)) 45 | } 46 | old := e.History 47 | e.History = make(map[int]*Environment, len(ps)) 48 | for _, p := range ps { 49 | v, ok := old[p.ID()] 50 | if !ok { 51 | cln := e.Environment.clone() 52 | v := &cln 53 | v.init() 54 | } 55 | e.History[p.ID()] = v 56 | } 57 | return nil 58 | } 59 | 60 | func (e *RTEvaluator) Takedown() error { 61 | 62 | // Collect the endpoints 63 | pts := make([]Point, len(e.History)) 64 | for _, env := range e.History { 65 | pts = append(pts, env.Hero.Location) 66 | } 67 | 68 | // Create maze image with all endpints (cumulative accross all iterations) 69 | var t string 70 | if e.useTrial { 71 | t = fmt.Sprintf("-%d", e.trialNum) 72 | } 73 | env := e.Environment.clone() 74 | return showMaze(path.Join(*WorkPath, fmt.Sprintf("maze%s-end-points.svg", t)), env, nil, pts) 75 | } 76 | 77 | func (e *RTEvaluator) Evaluate(p neat.Phenome) (r neat.Result) { 78 | 79 | // Retrieve the environment 80 | env := e.History[p.ID()] 81 | h := &env.Hero 82 | 83 | // Iterate the maze 1 step 84 | var err error 85 | paths := make([]Line, 0, *Steps) 86 | stop := false 87 | 88 | // Note the start of the path 89 | a := h.Location 90 | 91 | // Update the hero's location 92 | var outputs []float64 93 | inputs := generateNeuralInputs(*env) 94 | if outputs, err = p.Activate(inputs); err == nil { 95 | interpretOutputs(env, outputs[0], outputs[1]) 96 | update(env) 97 | b := h.Location 98 | paths = append(paths, Line{A: a, B: b}) 99 | 100 | // Look for solution 101 | stop = distanceToTarget(env) < 5.0 102 | } 103 | 104 | f := 300.0 - distanceToTarget(env) // fitness 105 | bh := []float64{h.Location.X, h.Location.Y} // behavior 106 | r = &Result{Classic: result.New(p.ID(), f, err, stop), behavior: bh} 107 | 108 | // Output the maze 109 | if e.show { 110 | // Write the file 111 | var t string 112 | if e.useTrial { 113 | t = fmt.Sprintf("-%d", e.trialNum) 114 | } 115 | if err := showMaze(path.Join(*WorkPath, fmt.Sprintf("maze%s-%d.svg", t, p.ID())), *env, paths, nil); err != nil { 116 | log.Println("Could not output maze run:", err) 117 | } 118 | } 119 | return 120 | } 121 | -------------------------------------------------------------------------------- /x/examples/maze/run.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | package main 27 | 28 | import ( 29 | "bufio" 30 | "flag" 31 | "fmt" 32 | "log" 33 | "os" 34 | "strconv" 35 | "strings" 36 | 37 | "github.com/rqme/neat" 38 | "github.com/rqme/neat/generator" 39 | "github.com/rqme/neat/result" 40 | "github.com/rqme/neat/searcher" 41 | "github.com/rqme/neat/x/starter" 42 | "github.com/rqme/neat/x/trials" 43 | ) 44 | 45 | var ( 46 | MazeFile = flag.String("maze", "medium_maze.txt", "Maze file to use in the experiment") 47 | Steps = flag.Int("steps", 400, "Number of steps a hero has to solve the maze") 48 | Novelty = flag.Bool("novelty", false, "Use novelty search instead of objective fitness") 49 | RealTime = flag.Bool("realtime", false, "Use the real-time generator and evaluator") 50 | WorkPath = flag.String("work-path", ".", "Output directory for maze diagrams") 51 | ) 52 | 53 | type Result struct { 54 | result.Classic 55 | behavior []float64 56 | } 57 | 58 | func (r *Result) Behavior() []float64 { return r.behavior } 59 | 60 | func loadEnv(p string) (e Environment, err error) { 61 | var f *os.File 62 | f, err = os.Open(p) 63 | if err != nil { 64 | return 65 | } 66 | defer f.Close() 67 | 68 | s := bufio.NewScanner(f) 69 | var n, i int 70 | var parts []string 71 | for s.Scan() { 72 | t := s.Text() 73 | switch i { 74 | case 0: // Number of lines 75 | if n, err = strconv.Atoi(t); err != nil { 76 | return 77 | } 78 | e.Lines = make([]Line, 0, n) 79 | case 1: // Start position 80 | parts = strings.Split(t, " ") 81 | if e.Hero.Location.X, err = strconv.ParseFloat(parts[0], 64); err != nil { 82 | return 83 | } 84 | if e.Hero.Location.Y, err = strconv.ParseFloat(parts[1], 64); err != nil { 85 | return 86 | } 87 | case 2: // Initial heading 88 | if e.Hero.Heading, err = strconv.ParseFloat(t, 64); err != nil { 89 | return 90 | } 91 | case 3: // End position 92 | parts = strings.Split(t, " ") 93 | if e.End.X, err = strconv.ParseFloat(parts[0], 64); err != nil { 94 | return 95 | } 96 | if e.End.Y, err = strconv.ParseFloat(parts[1], 64); err != nil { 97 | return 98 | } 99 | default: // Line 100 | var line Line 101 | parts := strings.Split(t, " ") 102 | if line.A.X, err = strconv.ParseFloat(parts[0], 64); err != nil { 103 | return 104 | } 105 | if line.A.Y, err = strconv.ParseFloat(parts[1], 64); err != nil { 106 | return 107 | } 108 | if line.B.X, err = strconv.ParseFloat(parts[2], 64); err != nil { 109 | return 110 | } 111 | if line.B.Y, err = strconv.ParseFloat(parts[3], 64); err != nil { 112 | return 113 | } 114 | e.Lines = append(e.Lines, line) 115 | } 116 | i += 1 117 | } 118 | return 119 | } 120 | 121 | func main() { 122 | flag.Parse() 123 | //defer profile.Start(profile.CPUProfile).Stop() 124 | if *Novelty { 125 | fmt.Println("Using Novelty search") 126 | } else { 127 | fmt.Println("Using Fitness search") 128 | } 129 | if *RealTime { 130 | fmt.Println("Using Real-Time generator") 131 | } else { 132 | fmt.Println("Using Classic generator") 133 | } 134 | fmt.Println("Using", *Steps, "time steps per evaluation") 135 | fmt.Println("Loading maze file:", *MazeFile) 136 | var err error 137 | orig := Evaluator{} 138 | if orig.Environment, err = loadEnv(*MazeFile); err != nil { 139 | log.Fatalf("Could not load maze file %s: %v", *MazeFile, err) 140 | } 141 | 142 | if err = trials.Run(func(i int) (*neat.Experiment, error) { 143 | 144 | var ctx *starter.Context 145 | var eval neat.Evaluator 146 | var gen neat.Generator 147 | if *RealTime { 148 | eval = &RTEvaluator{Evaluator: Evaluator{Environment: orig.clone()}} 149 | gen = &generator.RealTime{RealTimeSettings: ctx} 150 | } else { 151 | eval = &Evaluator{Environment: orig.clone()} 152 | gen = &generator.Classic{ClassicSettings: ctx} 153 | } 154 | if *Novelty { 155 | ctx = starter.NewContext(eval, func(ctx *starter.Context) { 156 | ctx.SetGenerator(gen) 157 | ctx.SetSearcher(&searcher.Novelty{NoveltySettings: ctx, Searcher: &searcher.Concurrent{}}) 158 | }) 159 | } else { 160 | ctx = starter.NewContext(eval, func(ctx *starter.Context) { 161 | ctx.SetGenerator(gen) 162 | }) 163 | } 164 | 165 | if exp, err := starter.NewExperiment(ctx, ctx, i); err != nil { 166 | return nil, err 167 | } else { 168 | //ctx.Settings.ArchivePath = path.Join(ctx.Settings.ArchivePath, "fit") 169 | //ctx.Settings.ArchiveName = ctx.Settings.ArchiveName + "-fit" 170 | //ctx.Settings.WebPath = path.Join(ctx.Settings.WebPath, "fit") 171 | return exp, nil 172 | } 173 | }); err != nil { 174 | log.Fatal("Could not run maze experiment: ", err) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /x/examples/ocr/ocr-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExperimentName": "OCR", 3 | "PopulationSize": 150, 4 | "Iterations": 100000000, 5 | "NumInputs": 35, 6 | "NumOutputs": 26, 7 | "FitnessType": 0, 8 | 9 | "TargetNumberOfSpecies": 15, 10 | "CompatibilityModifier": 0.3, 11 | "CompatibilityThreshold": 3.0, 12 | 13 | "DisjointCoefficient": 1, 14 | "ExcessCoefficient": 1, 15 | "WeightCoefficient": 0.4, 16 | 17 | "AddConnProbability": 0.025, 18 | "AddNodeProbability": 0.015, 19 | "EnableProbability": 0.2, 20 | "HiddenActivation": 2, 21 | "MutateOnlyProbability": 0.25, 22 | "MutateSettingProbability": 0, 23 | "MutateTraitProbability": 0, 24 | "MutateWeightProbability": 0.9, 25 | "ReplaceSettingProbability": 0, 26 | "ReplaceTraitProbability": 0, 27 | "ReplaceWeightProbability": 0.2, 28 | "WeightRange": 2.5, 29 | 30 | "DelConnProbability": 0.025, 31 | "DelNodeProbability": 0.015, 32 | "PruningPhaseThreshold": 30, 33 | "MaxMPCAge": 10, 34 | "MaxImprovementAge": 10, 35 | 36 | "InterspeciesMatingRate": 0.001, 37 | "MateByAveragingProbability": 0.4, 38 | "MaxStagnation": 15, 39 | "OutputActivation": 2, 40 | "SurvivalThreshold": 0.2, 41 | 42 | "ArchivePath": "/tmp/ocr", 43 | "WebPath": "/tmp/ocr" 44 | } -------------------------------------------------------------------------------- /x/examples/ocr/ocr.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package main 28 | 29 | import ( 30 | "bytes" 31 | "flag" 32 | "fmt" 33 | "log" 34 | "math" 35 | "time" 36 | 37 | "github.com/montanaflynn/stats" 38 | "github.com/rqme/neat" 39 | "github.com/rqme/neat/mutator" 40 | "github.com/rqme/neat/result" 41 | "github.com/rqme/neat/x/starter" 42 | "github.com/rqme/neat/x/trials" 43 | ) 44 | 45 | var ( 46 | Phased = flag.Bool("phased", false, "Use the phased mutator during evolution") 47 | Duration = flag.Int("duration", 90, "Maximum duration in minutes of each trial") 48 | ) 49 | 50 | type Evaluator struct { 51 | stopTime time.Time 52 | show bool 53 | useTrial bool 54 | trialNum int 55 | } 56 | 57 | func (e *Evaluator) SetTrial(t int) error { 58 | e.useTrial = true 59 | e.trialNum = t 60 | return nil 61 | } 62 | 63 | func (e *Evaluator) ShowWork(s bool) { 64 | e.show = s 65 | } 66 | 67 | func (e Evaluator) Evaluate(p neat.Phenome) (r neat.Result) { 68 | 69 | // Iterate the inputs and ask about 70 | letters := []uint8{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'} 71 | guesses := make([][]uint8, 26) 72 | values := make([]float64, 26) 73 | sum := 0.0 74 | cnt := 0.0 75 | stop := true 76 | for i, input := range inputs { 77 | // Query the network for this letter 78 | outputs, err := p.Activate(input) 79 | if err != nil { 80 | return result.New(p.ID(), 0, err, false) 81 | } 82 | 83 | // Identify the max 84 | max, _ := stats.Max(outputs) 85 | values[i] = max 86 | 87 | // Determine success 88 | s2 := 0.0 89 | for j := 0; j < len(outputs); j++ { 90 | if outputs[j] == max { 91 | guesses[i] = append(guesses[i], letters[j]) 92 | if j != i { 93 | stop = false // picked another letter 94 | s2 += 1.0 95 | } else { 96 | s2 += 1.0 - max 97 | } 98 | } 99 | cnt += 1 100 | } 101 | sum += s2 102 | } 103 | 104 | if e.show { 105 | b := bytes.NewBufferString("\n") 106 | fmt.Println() 107 | if e.useTrial { 108 | b.WriteString(fmt.Sprintf("Trial %d ", e.trialNum)) 109 | } 110 | b.WriteString(fmt.Sprintf("OCR Evaluation for genome %d. Letter->Guess(confidence)\n", p.ID())) 111 | b.WriteString(fmt.Sprintf("------------------------------------------\n")) 112 | for i := 0; i < len(letters); i++ { 113 | b.WriteString(fmt.Sprintf("%s (%0.2f)", string(letters[i]), values[i])) 114 | cl := "" 115 | il := "" 116 | for j := 0; j < len(guesses[i]); j++ { 117 | if guesses[i][j] == letters[i] { 118 | cl = " correct" 119 | } else { 120 | if il != "" { 121 | il += ", " 122 | } 123 | il += string(guesses[i][j]) 124 | } 125 | } 126 | if cl == "" { 127 | cl = " incorrect" 128 | } 129 | b.WriteString(cl) 130 | if il != "" { 131 | b.WriteString(" but also guessed ") 132 | b.WriteString(il) 133 | } 134 | b.WriteString("\n") 135 | } 136 | fmt.Println(b.String()) 137 | } 138 | 139 | return result.New(p.ID(), math.Pow(cnt-sum, 2), nil, stop || !time.Now().Before(e.stopTime)) 140 | } 141 | 142 | func main() { 143 | flag.Parse() 144 | //defer profile.Start(profile.CPUProfile).Stop() 145 | 146 | if *Phased { 147 | fmt.Println("Using phased mutator") 148 | } else { 149 | fmt.Println("Using complexifying-only mutator") 150 | } 151 | fmt.Println("Each trial will run for a maximum of", *Duration, "minutes.") 152 | 153 | if err := trials.Run(func(i int) (*neat.Experiment, error) { 154 | eval := &Evaluator{stopTime: time.Now().Add(time.Minute * time.Duration(*Duration))} 155 | var ctx *starter.Context 156 | if *Phased { 157 | ctx = starter.NewContext(eval, func(ctx *starter.Context) { 158 | ctx.SetMutator(mutator.NewComplete(ctx, ctx, ctx, ctx, ctx, ctx)) 159 | }) 160 | } else { 161 | ctx = starter.NewContext(eval) 162 | } 163 | if exp, err := starter.NewExperiment(ctx, ctx, i); err != nil { 164 | return nil, err 165 | } else { 166 | return exp, nil 167 | } 168 | 169 | }); err != nil { 170 | log.Fatal("Could not run OCR: ", err) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /x/examples/xor/xor-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExperimentName": "XOR", 3 | "PopulationSize": 10, 4 | "Iterations": 100, 5 | "NumInputs": 2, 6 | "NumOutputs": 1, 7 | "FitnessType": 1, 8 | 9 | "TargetNumberOfSpecies": 15, 10 | "CompatibilityModifier": 0.3, 11 | "CompatibilityThreshold": 3.0, 12 | 13 | "DisjointCoefficient": 1, 14 | "ExcessCoefficient": 1, 15 | "WeightCoefficient": 0.4, 16 | 17 | "AddConnProbability": 0.025, 18 | "AddNodeProbability": 0.015, 19 | "EnableProbability": 0.2, 20 | "HiddenActivation": 2, 21 | "MutateOnlyProbability": 0.25, 22 | "MutateSettingProbability": 0, 23 | "MutateTraitProbability": 0, 24 | "MutateWeightProbability": 0.9, 25 | "ReplaceSettingProbability": 0, 26 | "ReplaceTraitProbability": 0, 27 | "ReplaceWeightProbability": 0.2, 28 | "WeightRange": 2.5, 29 | 30 | "InterspeciesMatingRate": 0.001, 31 | "MateByAveragingProbability": 0.4, 32 | "MaxStagnation": 15, 33 | "OutputActivation": 2, 34 | "SurvivalThreshold": 0.2, 35 | 36 | "ArchivePath": "/tmp/xor", 37 | "WebPath": "/tmp/xor" 38 | } -------------------------------------------------------------------------------- /x/examples/xor/xor.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package main 28 | 29 | import ( 30 | "bytes" 31 | "flag" 32 | "fmt" 33 | "log" 34 | "math" 35 | 36 | "github.com/rqme/neat" 37 | "github.com/rqme/neat/result" 38 | "github.com/rqme/neat/x/starter" 39 | "github.com/rqme/neat/x/trials" 40 | ) 41 | 42 | type Evaluator struct { 43 | show bool 44 | useTrial bool 45 | trialNum int 46 | } 47 | 48 | func (e *Evaluator) SetTrial(t int) error { 49 | e.useTrial = true 50 | e.trialNum = t 51 | return nil 52 | } 53 | 54 | // Evaluate computes the error for the XOR problem with the phenome 55 | // 56 | // To compute fitness, the distance of the output from the correct answer was summed for all four 57 | // input patterns. The result of this error was subtracted from 4 so that higher fitness would mean 58 | // better networks. The resulting number was squared to give proportionally more fitness the closer 59 | // a network was to a solution. (Stanley, 43) 60 | func (e Evaluator) Evaluate(p neat.Phenome) (r neat.Result) { 61 | inputs := [][]float64{ 62 | []float64{0, 0}, 63 | []float64{0, 1}, 64 | []float64{1, 0}, 65 | []float64{1, 1}, 66 | } 67 | 68 | expected := []float64{0, 1, 1, 0} 69 | actual := make([]float64, 4) 70 | 71 | // Run experiment 72 | var err error 73 | var sum float64 74 | stop := true 75 | for i, in := range inputs { 76 | outputs, err := p.Activate(in) 77 | if err != nil { 78 | break 79 | } 80 | actual[i] = outputs[0] 81 | sum += math.Abs(outputs[0] - expected[i]) 82 | if expected[i] == 0 { 83 | stop = stop && outputs[0] < 0.5 84 | } else { 85 | stop = stop && outputs[0] > 0.5 86 | } 87 | } 88 | 89 | // Display the work 90 | if e.show { 91 | b := bytes.NewBufferString("\n") 92 | if e.useTrial { 93 | b.WriteString(fmt.Sprintf("Trial %d ", e.trialNum)) 94 | } 95 | b.WriteString(fmt.Sprintf("XOR Evaluation for genome %d\n", p.ID())) 96 | b.WriteString(fmt.Sprintf("------------------------------------------\n")) 97 | b.WriteString(fmt.Sprintf("For {0,0}, expected 0. output was %f\n", actual[0])) 98 | b.WriteString(fmt.Sprintf("For {0,1}, expected 1. output was %f\n", actual[1])) 99 | b.WriteString(fmt.Sprintf("For {1,0}, expected 1. output was %f\n", actual[2])) 100 | b.WriteString(fmt.Sprintf("For {1,1}, expected 0. output was %f\n", actual[3])) 101 | fmt.Print(b.String()) 102 | } 103 | 104 | // Calculate the result 105 | if stop { 106 | fmt.Println("Stopping", p.ID()) 107 | } 108 | r = result.New(p.ID(), math.Pow(4.0-sum, 2.0), err, stop) 109 | return 110 | } 111 | 112 | func (e *Evaluator) ShowWork(s bool) { 113 | e.show = s 114 | } 115 | 116 | func main() { 117 | flag.Parse() 118 | //defer profile.Start(profile.CPUProfile).Stop() 119 | if err := trials.Run(func(i int) (*neat.Experiment, error) { 120 | ctx := starter.NewContext(&Evaluator{}) 121 | if exp, err := starter.NewExperiment(ctx, ctx, i); err != nil { 122 | return nil, err 123 | } else { 124 | return exp, nil 125 | } 126 | 127 | }); err != nil { 128 | log.Fatal("Could not run XOR: ", err) 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /x/proof/Readme.md: -------------------------------------------------------------------------------- 1 | Proof 2 | ===== 3 | 4 | Each new enhancement is verified by creating by solving the XOR experiment. This does not necessarily highlight the feature but should provide a reasonable checkpoint for moving forward. -------------------------------------------------------------------------------- /x/proof/eshyperneat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func runESHyperNEAT() { 4 | /* 5 | // ESHyperNEAT decoder settings 6 | InitialDepth int 7 | MaxDepth int 8 | DivisionThreshold float64 9 | VarianceThreshold float64 10 | BandThreshold float64 11 | IterationLevels int 12 | */ 13 | } 14 | -------------------------------------------------------------------------------- /x/proof/hyperneat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/rqme/neat" 5 | "github.com/rqme/neat/decoder" 6 | "github.com/rqme/neat/mutator" 7 | "github.com/rqme/neat/x/starter" 8 | ) 9 | 10 | type SettingsWithLayers struct { 11 | decoder.ESHyperNEATSettings 12 | layers []decoder.SubstrateNodes 13 | } 14 | 15 | func (h SettingsWithLayers) SubstrateLayers() []decoder.SubstrateNodes { return h.layers } 16 | 17 | func hyperneatSettings(cfg decoder.ESHyperNEATSettings) (hns SettingsWithLayers) { 18 | hns.ESHyperNEATSettings = cfg 19 | 20 | hns.layers = make([]decoder.SubstrateNodes, 3) 21 | hns.layers[0] = []decoder.SubstrateNode{ 22 | {Position: []float64{-1.0, 0.0}, NeuronType: neat.Input}, 23 | {Position: []float64{1.0, 0.0}, NeuronType: neat.Input}, 24 | } 25 | hns.layers[1] = []decoder.SubstrateNode{ 26 | {Position: []float64{-1.0, 0.0}, NeuronType: neat.Hidden}, 27 | {Position: []float64{1.0, 0.0}, NeuronType: neat.Hidden}, 28 | } 29 | hns.layers[2] = []decoder.SubstrateNode{ 30 | {Position: []float64{0.0, 0.0}, NeuronType: neat.Output}, 31 | } 32 | return 33 | } 34 | 35 | func hyperneatContext() *starter.Context { 36 | cfg := initSettings() 37 | cfg.ExperimentName = "HyperNEAT" 38 | cfg.ArchivePath = "./proof-out/hyperneat" 39 | cfg.ArchiveName = "hyperneat" 40 | cfg.WebPath = cfg.ArchivePath 41 | cfg.MutateActivationProbability = 0.25 42 | cfg.OutputActivation = neat.Tanh 43 | cfg.NumInputs = 4 44 | cfg.NumOutputs = 2 45 | 46 | ctx := starter.NewContext(&NEATEval{}, func(ctx *starter.Context) { 47 | ctx.SetMutator(mutator.NewComplete(ctx, ctx, ctx, ctx, ctx, ctx)) 48 | ctx.SetDecoder(&decoder.HyperNEAT{CppnDecoder: decoder.Classic{}, HyperNEATSettings: hyperneatSettings(ctx)}) 49 | }) 50 | ctx.Settings = cfg 51 | return ctx 52 | } 53 | -------------------------------------------------------------------------------- /x/proof/neat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/rqme/neat" 7 | "github.com/rqme/neat/result" 8 | "github.com/rqme/neat/x/starter" 9 | ) 10 | 11 | type NEATEval struct{} 12 | 13 | func (e *NEATEval) Evaluate(p neat.Phenome) (r neat.Result) { 14 | inputs := [][]float64{{0, 0}, {0, 1}, {1, 0}, {1, 1}} 15 | expected := []float64{0, 1, 1, 0} 16 | actual := make([]float64, 4) 17 | 18 | // Run experiment 19 | var err error 20 | var sum float64 21 | stop := true 22 | for i, in := range inputs { 23 | outputs, err := p.Activate(in) 24 | if err != nil { 25 | break 26 | } 27 | actual[i] = outputs[0] 28 | sum += math.Abs(outputs[0] - expected[i]) 29 | if expected[i] == 0 { 30 | stop = stop && outputs[0] < 0.5 31 | } else { 32 | stop = stop && outputs[0] > 0.5 33 | } 34 | } 35 | 36 | // Calculate the result 37 | r = result.New(p.ID(), math.Pow(4.0-sum, 2.0), err, stop) 38 | return 39 | } 40 | 41 | func neatContext() *starter.Context { 42 | cfg := initSettings() 43 | cfg.ExperimentName = "NEAT" 44 | cfg.ArchivePath = "./proof-out/neat" 45 | cfg.ArchiveName = "neat" 46 | cfg.WebPath = cfg.ArchivePath 47 | 48 | ctx := starter.NewContext(&NEATEval{}) 49 | ctx.Settings = cfg 50 | return ctx 51 | } 52 | -------------------------------------------------------------------------------- /x/proof/novelty.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func runNovelty() { 4 | /* 5 | // Novelty searcher settings 6 | NoveltyEvalArchive bool 7 | NoveltyArchiveThreshold float64 8 | NumNearestNeighbors int 9 | */ 10 | } 11 | -------------------------------------------------------------------------------- /x/proof/phased.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/rqme/neat" 5 | "github.com/rqme/neat/x/starter" 6 | ) 7 | 8 | func phasedContext() *starter.Context { 9 | cfg := initSettings() 10 | cfg.ExperimentName = "Phased" 11 | cfg.ArchivePath = "./proof-out/phased" 12 | cfg.ArchiveName = "phased" 13 | cfg.WebPath = cfg.ArchivePath 14 | cfg.PruningPhaseThreshold = 10 15 | cfg.MaxMPCAge = 5 16 | cfg.MaxImprovementAge = 5 17 | cfg.ImprovementType = neat.Absolute 18 | cfg.DelNodeProbability = 0.015 19 | cfg.DelConnProbability = 0.025 20 | 21 | ctx := starter.NewContext(&NEATEval{}) 22 | ctx.Settings = cfg 23 | return ctx 24 | } 25 | -------------------------------------------------------------------------------- /x/proof/proof.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "sync" 7 | 8 | "github.com/rqme/neat" 9 | "github.com/rqme/neat/decoder" 10 | "github.com/rqme/neat/x/starter" 11 | ) 12 | 13 | const trials = 10 14 | 15 | func main2() { 16 | // create the genome 17 | g := neat.Genome{ID: 725, Nodes: make(map[int]neat.Node), Conns: make(map[int]neat.Connection)} 18 | g.Nodes[1] = neat.Node{Innovation: 1, X: 0, Y: 0, NeuronType: neat.Bias, ActivationType: neat.Direct} 19 | g.Nodes[2] = neat.Node{Innovation: 2, X: 0.5, Y: 0, NeuronType: neat.Input, ActivationType: neat.Direct} 20 | g.Nodes[3] = neat.Node{Innovation: 3, X: 1.0, Y: 0, NeuronType: neat.Input, ActivationType: neat.Direct} 21 | g.Nodes[4] = neat.Node{Innovation: 4, X: 0.5, Y: 1.0, NeuronType: neat.Output, ActivationType: neat.SteependSigmoid} 22 | g.Nodes[64] = neat.Node{Innovation: 64, X: 0.5, Y: 0.5, NeuronType: neat.Hidden, ActivationType: neat.SteependSigmoid} 23 | g.Nodes[234] = neat.Node{Innovation: 234, X: 0.5, Y: 0.25, NeuronType: neat.Hidden, ActivationType: neat.SteependSigmoid} 24 | g.Conns[5] = neat.Connection{Innovation: 5, Source: 1, Target: 4, Enabled: true, Weight: 2.4734613949471784} 25 | g.Conns[6] = neat.Connection{Innovation: 6, Source: 2, Target: 4, Enabled: false, Weight: -5.64995113868551} 26 | g.Conns[7] = neat.Connection{Innovation: 7, Source: 3, Target: 4, Enabled: true, Weight: 0.8428774810124069} 27 | g.Conns[65] = neat.Connection{Innovation: 65, Source: 2, Target: 64, Enabled: false, Weight: 1.6538892148212083} 28 | g.Conns[66] = neat.Connection{Innovation: 66, Source: 64, Target: 4, Enabled: true, Weight: -2.9999856850356714} 29 | g.Conns[84] = neat.Connection{Innovation: 84, Source: 3, Target: 64, Enabled: true, Weight: 3.9055228465697596} 30 | g.Conns[159] = neat.Connection{Innovation: 159, Source: 1, Target: 64, Enabled: true, Weight: 0.350296980355459} 31 | g.Conns[235] = neat.Connection{Innovation: 235, Source: 2, Target: 234, Enabled: true, Weight: 6.111340339072617} 32 | g.Conns[236] = neat.Connection{Innovation: 236, Source: 234, Target: 64, Enabled: true, Weight: -2.1660946640041074} 33 | g.Conns[335] = neat.Connection{Innovation: 335, Source: 234, Target: 4, Enabled: true, Weight: -0.338797031578537} 34 | g.Conns[463] = neat.Connection{Innovation: 463, Source: 1, Target: 234, Enabled: true, Weight: -3.4407146147284307} 35 | 36 | // decode the genome 37 | d := &decoder.Classic{} 38 | p, _ := d.Decode(g) 39 | 40 | // Evaluate 41 | e := &NEATEval{} 42 | r := e.Evaluate(p) 43 | fmt.Println(r) 44 | } 45 | func main() { 46 | log.Println("Running proofs. Success rates under 100% are OK.") 47 | run("neat", neatContext) 48 | run("phased", phasedContext) 49 | run("hyperneat", hyperneatContext) 50 | } 51 | 52 | func run(name string, f func() *starter.Context) { 53 | wg := new(sync.WaitGroup) 54 | ch := make(chan float64, trials) 55 | for i := 0; i < trials; i++ { 56 | wg.Add(1) 57 | go func() { 58 | ctx := f() 59 | exp := &neat.Experiment{ExperimentSettings: ctx} 60 | exp.SetContext(ctx) 61 | if err := neat.Run(exp); err != nil { 62 | log.Fatalf("Fatal error in %s: %v\n", name, err) 63 | } 64 | if exp.Stopped() { 65 | ch <- 0.0 66 | } else { 67 | ch <- 1.0 68 | } 69 | wg.Done() 70 | }() 71 | } 72 | wg.Wait() 73 | 74 | sum := 0.0 75 | for i := 0; i < trials; i++ { 76 | sum += <-ch 77 | } 78 | log.Println(name, "success rate:", sum/10.0) 79 | } 80 | -------------------------------------------------------------------------------- /x/proof/realtime.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func runRealTime() { 4 | /* 5 | 6 | // Real-Time generator settings 7 | IneligiblePercent float64 8 | MinimumTimeAlive int 9 | */ 10 | } 11 | -------------------------------------------------------------------------------- /x/proof/settings.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/rqme/neat" 5 | "github.com/rqme/neat/x/starter" 6 | ) 7 | 8 | func initSettings() starter.Settings { 9 | return starter.Settings{ 10 | // Experiment settings 11 | Iterations: 100, 12 | FitnessType: neat.Absolute, 13 | 14 | // Classic comparer settings 15 | DisjointCoefficient: 1.0, 16 | ExcessCoefficient: 1.0, 17 | WeightCoefficient: 1.0, 18 | 19 | // Classic crosser settings 20 | EnableProbability: 0.2, 21 | MateByAveragingProbability: 0.4, 22 | 23 | // Classic generator settings 24 | PopulationSize: 150, 25 | NumInputs: 2, 26 | NumOutputs: 1, 27 | OutputActivation: neat.SteependSigmoid, 28 | HiddenActivation: neat.SteependSigmoid, 29 | WeightRange: 2.5, 30 | SurvivalThreshold: 0.2, 31 | MutateOnlyProbability: 0.25, 32 | InterspeciesMatingRate: 0.001, 33 | MaxStagnation: 15, 34 | 35 | // Classic mutator settings 36 | MutateWeightProbability: 0.9, 37 | ReplaceWeightProbability: 0.2, 38 | AddNodeProbability: 0.125, 39 | AddConnProbability: 0.025, 40 | 41 | // Classic speciater settings 42 | CompatibilityThreshold: 3.0, 43 | TargetNumberOfSpecies: 15, 44 | CompatibilityModifier: 0.3, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /x/starter/experiment.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | package starter 28 | 29 | import ( 30 | "flag" 31 | "os" 32 | 33 | "github.com/rqme/neat" 34 | "github.com/rqme/neat/archiver" 35 | ) 36 | 37 | const ( 38 | NoTrials int = -1 39 | ) 40 | 41 | var ( 42 | ConfigPath = flag.String("config-path", "", "Path to configuration file to override archive.") 43 | ConfigName = flag.String("config-name", "", "Name prepended to all configuration and state files") 44 | ) 45 | 46 | type ConfigSettings struct { 47 | path, name string 48 | } 49 | 50 | func (s ConfigSettings) ArchiveName() string { return s.name } 51 | func (s ConfigSettings) ArchivePath() string { return s.path } 52 | 53 | func NewExperiment(ctx neat.Context, cfg neat.ExperimentSettings, t int) (exp *neat.Experiment, err error) { 54 | 55 | // Create the experiment 56 | exp = &neat.Experiment{ExperimentSettings: cfg} 57 | exp.SetContext(ctx) 58 | 59 | // Restore the saved setting and, if available, state 60 | if *ConfigName == "" { 61 | *ConfigName = os.Args[0] // Use the executable's name 62 | } 63 | rst := &archiver.File{ 64 | FileSettings: ConfigSettings{path: *ConfigPath, name: *ConfigName}, 65 | } 66 | if err = rst.Restore(ctx); err != nil { 67 | return 68 | } 69 | 70 | // Update helpers with trial number 71 | if t > NoTrials { 72 | hs := []interface{}{ 73 | ctx.Archiver(), 74 | ctx.Comparer(), 75 | ctx.Crosser(), 76 | ctx.Decoder(), 77 | ctx.Evaluator(), 78 | ctx.Generator(), 79 | ctx.Mutator(), 80 | ctx.Searcher(), 81 | ctx.Speciater(), 82 | ctx.Visualizer(), 83 | } 84 | for _, h := range hs { 85 | if th, ok := h.(neat.Trialable); ok { 86 | th.SetTrial(t) 87 | } 88 | } 89 | } 90 | 91 | // Load ids and innovations 92 | if ph, ok := ctx.(neat.Populatable); ok { 93 | ph.SetPopulation(exp.Population()) 94 | } 95 | return 96 | } 97 | -------------------------------------------------------------------------------- /x/starter/identify.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | package starter 27 | 28 | import ( 29 | "sync" 30 | 31 | "github.com/rqme/neat" 32 | ) 33 | 34 | type innovation struct { 35 | Type neat.InnoType 36 | Key neat.InnoKey 37 | } 38 | 39 | type identify struct { 40 | sync.Mutex 41 | lastID int 42 | innos map[innovation]int 43 | } 44 | 45 | // NextID returns the next id in the context's sequence 46 | func (x *identify) NextID() int { 47 | x.Lock() 48 | defer x.Unlock() 49 | x.lastID += 1 50 | return x.lastID 51 | } 52 | 53 | // Returns the innovation number for this type and key 54 | func (x *identify) Innovation(t neat.InnoType, k neat.InnoKey) int { 55 | x.Lock() 56 | defer x.Unlock() 57 | var id int 58 | var ok bool 59 | 60 | in := innovation{Type: t, Key: k} 61 | if id, ok = x.innos[in]; !ok { 62 | x.lastID += 1 63 | id = x.lastID 64 | x.innos[in] = id 65 | } 66 | return id 67 | } 68 | 69 | // Initializes the ID sequence and innovation history 70 | func (x *identify) SetPopulation(p neat.Population) error { 71 | for _, g := range p.Genomes { 72 | if g.ID > x.lastID { 73 | x.lastID = g.ID 74 | } 75 | for _, n := range g.Nodes { 76 | if n.Innovation > x.lastID { 77 | x.lastID = n.Innovation 78 | } 79 | x.innos[innovation{Type: neat.NodeInnovation, Key: n.Key()}] = n.Innovation 80 | } 81 | for _, c := range g.Conns { 82 | if c.Innovation > x.lastID { 83 | x.lastID = c.Innovation 84 | } 85 | x.innos[innovation{Type: neat.ConnInnovation, Key: c.Key()}] = c.Innovation 86 | } 87 | } 88 | return nil 89 | } 90 | -------------------------------------------------------------------------------- /x/starter/settings.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2015, Brian Hummer (brian@redq.me) 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | package starter 27 | 28 | import ( 29 | "bytes" 30 | "fmt" 31 | "reflect" 32 | 33 | "github.com/rqme/neat" 34 | "github.com/rqme/neat/decoder" 35 | ) 36 | 37 | type Settings struct { 38 | 39 | // Experiment settings 40 | Iterations int 41 | Traits neat.Traits 42 | FitnessType neat.FitnessType 43 | ExperimentName string 44 | 45 | // File archiver settings 46 | ArchivePath string 47 | ArchiveName string 48 | 49 | // Classic comparer settings 50 | DisjointCoefficient float64 51 | ExcessCoefficient float64 52 | WeightCoefficient float64 53 | 54 | // Classic crosser settings 55 | EnableProbability float64 56 | MateByAveragingProbability float64 57 | 58 | // HyperNEAT decoder settings 59 | SubstrateLayers []decoder.SubstrateNodes 60 | 61 | // ESHyperNEAT decoder settings 62 | InitialDepth int 63 | MaxDepth int 64 | DivisionThreshold float64 65 | VarianceThreshold float64 66 | BandThreshold float64 67 | IterationLevels int 68 | 69 | // Classic generator settings 70 | PopulationSize int 71 | NumInputs int 72 | NumOutputs int 73 | OutputActivation neat.ActivationType 74 | WeightRange float64 75 | SurvivalThreshold float64 76 | MutateOnlyProbability float64 77 | InterspeciesMatingRate float64 78 | MaxStagnation int 79 | SeedGenome neat.Genome 80 | 81 | // Real-Time generator settings 82 | IneligiblePercent float64 83 | MinimumTimeAlive int 84 | 85 | // Classic mutator settings 86 | MutateActivationProbability float64 // Probability that the node's activation will be mutated 87 | MutateWeightProbability float64 // Probability that the weight will be mutated 88 | ReplaceWeightProbability float64 // Probability that the weight will be replaced 89 | MutateTraitProbability float64 // Probability that the trait will be mutated 90 | ReplaceTraitProbability float64 // Probability that the trait will be replaced 91 | MutateSettingProbability float64 // Probability that the setting will be mutated 92 | ReplaceSettingProbability float64 // Probability that the setting will be replaced 93 | AddNodeProbability float64 // Probablity a node will be added to the genome 94 | AddConnProbability float64 // Probability a connection will be added to the genome 95 | HiddenActivation neat.ActivationType // Activation type to assign to new nodes 96 | DelNodeProbability float64 // Probablity a node will be removed to the genome 97 | DelConnProbability float64 // Probability a connection will be removed to the genome 98 | 99 | // Phased mutator settings 100 | PruningPhaseThreshold float64 101 | MaxMPCAge int 102 | MaxImprovementAge int 103 | ImprovementType neat.FitnessType 104 | 105 | // Novelty searcher settings 106 | NoveltyEvalArchive bool 107 | NoveltyArchiveThreshold float64 108 | NumNearestNeighbors int 109 | 110 | // Classic speciater settings 111 | CompatibilityThreshold float64 112 | TargetNumberOfSpecies int 113 | CompatibilityModifier float64 114 | 115 | // Web visualizer settings 116 | WebPath string 117 | } 118 | 119 | func (s Settings) String() string { 120 | b := bytes.NewBufferString("Settings:\n") 121 | v := reflect.ValueOf(s) 122 | t := v.Type() 123 | for i := 0; i < v.NumField(); i++ { 124 | b.WriteString(fmt.Sprintf("\t%s: %v\n", t.Field(i).Name, v.Field(i).Interface())) 125 | } 126 | return b.String() 127 | } 128 | --------------------------------------------------------------------------------