diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 5761136..ffcd629 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,4 @@ # These are supported funding model platforms patreon: io42 +custom: ["https://www.buymeacoffee.com/io42"] diff --git a/.github/codecov.yml b/.github/codecov.yml index 7897d5d..7528f3b 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,2 +1,2 @@ ignore: - - "examples/**/*" # ignoring experiments folder \ No newline at end of file + - "examples/**/*" # ignoring examples folder \ No newline at end of file diff --git a/.github/linters/.jscpd.json b/.github/linters/.jscpd.json new file mode 100644 index 0000000..112294e --- /dev/null +++ b/.github/linters/.jscpd.json @@ -0,0 +1,10 @@ +{ + "threshold": 20, + "reporters": [ + "consoleFull" + ], + "ignore": [ + "**/__snapshots__/**" + ], + "absolute": true +} \ No newline at end of file diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index fcca4ea..cc961a5 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,4 +1,4 @@ -name-template: 'v$RESOLVED_VERSION 🌈' +name-template: 'v$RESOLVED_VERSION 🇺🇦' tag-template: 'v$RESOLVED_VERSION' categories: - title: '🚀 Features' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d17d869..9cf0118 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,12 +11,12 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.17 + go-version: 1.21 - name: Build run: go build -v ./... @@ -29,4 +29,9 @@ jobs: GO111MODULE: on - name: Upload coverage to Codecov - run: bash <(curl -s https://codecov.io/bash) + uses: codecov/codecov-action@v4 + with: + files: ./coverage.txt + flags: unittests + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index fffc923..1e43d72 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -3,28 +3,50 @@ # # You can adjust the behavior by modifying this file. # For more information, see: -# https://github.com/github/super-linter +# https://github.com/golangci/golangci-lint-action name: Lint Code Base - on: push: - branches: [ master ] + tags: + - v* + branches: + - master + - main pull_request: - branches: [ master ] +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read jobs: - run-lint: + golangci: + name: lint runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v3 + - uses: actions/setup-go@v5 + with: + go-version: '1.19' + - uses: actions/checkout@v4 + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 with: - # Full git history is needed to get a proper list of changed files within `super-linter` - fetch-depth: 0 + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: v1.60 + + # Optional: working directory, useful for monorepos + # working-directory: somedir + + # Optional: golangci-lint command line arguments. + # args: --issues-exit-code=0 + + # Optional: show only new issues if it's a pull request. The default value is `false`. + # only-new-issues: true + + # Optional: if set to true then the all caching functionality will be complete disabled, + # takes precedence over all other caching options. + # skip-cache: true + + # Optional: if set to true then the action don't cache or restore ~/go/pkg. + # skip-pkg-cache: true - - name: Lint Code Base - uses: github/super-linter/slim@v4 - env: - VALIDATE_ALL_CODEBASE: false - DEFAULT_BRANCH: master - FILTER_REGEX_EXCLUDE: .*README.md - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. + # skip-build-cache: true diff --git a/.gitignore b/.gitignore index 037cbdc..b57b01d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ vendor # The IDEA project forlder .idea +/goESHyperNEAT.iml # The MacOS specific .DS_Store @@ -22,3 +23,4 @@ vendor # Output of the go coverage tool, specifically when used with LiteIDE *.out + diff --git a/Makefile b/Makefile index 40d5562..b96a26e 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ GORUN = $(GOCMD) run # The common parameters BINARY_NAME = eshyperneat OUT_DIR = out -LOG_LEVEL = -1 +LOG_LEVEL = info # # The retina experiment parameters diff --git a/README.md b/README.md index b98171e..255a4ea 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# goESHyperNEAT 🇺🇦 [![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://u24.gov.ua) + [![Build Status](https://travis-ci.org/yaricom/goESHyperNEAT.svg?branch=master)](https://travis-ci.org/yaricom/goESHyperNEAT) [![GoDoc](https://godoc.org/github.com/yaricom/goESHyperNEAT?status.svg)](https://godoc.org/github.com/yaricom/goESHyperNEAT) ## Overview @@ -15,22 +17,22 @@ by human before algorithm execution to reflect inherent geometrical topology of of hidden nodes in the network this leads to the reduction of algorithm efficiency due to lack of ability to estimate where CPPN generated patterns will have intersection with manually seeded nodes. -This drawback is addressed by **Evolved-Substrate HyperNEAT** method which allows to encode hidden nodes position +This drawback is addressed by **Evolvable-Substrate HyperNEAT** method which allows to encode hidden nodes position in the substrate in the CPPN generated patterns of weights. As additional benefit of this the substrate is able to evolve it's geometrical topology during training, producing regions with varying neural density, thereby providing a kind of scaffolding for situating cognitive structures in the biological brains. -## References: +## References 1. The original C++ NEAT implementation created by Kenneth O. Stanley, see: [NEAT][1] 2. Other NEAT implementations can be found at [NEAT Software Catalog][2] 3. [The ES-HyperNEAT Users Page][3] -4. Kenneth O. Stanley, David D’Ambrosio and Jason Gauci, [A Hypercube-Based Indirect Encoding for Evolving Large-Scale Neural Networks][4], Artificial Life journal 15(2), Cambridge, MA: MIT Press, 2009 -5. Sebastian Risi, Kenneth O. Stanley, [An Enhanced Hypercube-Based Encoding for Evolving the Placement, Density and Connectivity of Neurons][5], Artificial Life journal, Cambridge, MA: MIT Press, 2012 -6. Kenneth O. Stanley, [Ph.D. Dissertation: EFFICIENT EVOLUTION OF NEURAL NETWORKS THROUGH COMPLEXIFICATION][6], Department of Computer Sciences, The University of Texas at Austin, Technical Report~AI-TR-04–39, August 2004 -7. Kenneth O. Stanley, [Compositional Pattern Producing Networks: A Novel Abstraction of Development][7], Genetic Programming and Evolvable Machines, Special Issue on Developmental Systems, New York, NY: Springer, 2007 -8. Iaroslav Omelianenko, [The GoLang NEAT implementation][8], GitHub, 2018 +4. Kenneth O. Stanley, David D’Ambrosio and Jason Gauci, [A Hypercube-Based Indirect Encoding for Evolving Large-Scale Neural Networks][4], Artificial Life journal, Cambridge, MA: MIT Press, 2009, vol. 15, no. 2, pp. 185-212 +5. Sebastian Risi, Kenneth O. Stanley, [An Enhanced Hypercube-Based Encoding for Evolving the Placement, Density and Connectivity of Neurons][5], Artificial Life journal, Cambridge, MA: MIT Press, 2012, vol. 18, no. 4, pp. 331-363 +6. Kenneth O. Stanley, [Ph.D. Dissertation: Efficient Evolution of Neural Networks through Complexification][6], Department of Computer Sciences, The University of Texas at Austin, Technical Report~AI-TR-04–39, August 2004 +7. Kenneth O. Stanley, [Compositional Pattern Producing Networks: A Novel Abstraction of Development][7], Genetic Programming and Evolvable Machines, Special Issue on Developmental Systems, New York, NY: Springer, 2007, vol. 8, pp. 131-162 +8. Omelianenko, Iaroslav, [The GoLang implementation of NeuroEvolution of Augmented Topologies (NEAT) algorithm][8], GitHub, Computer software This source code maintained and managed by [Iaroslav Omelianenko][9] @@ -38,9 +40,9 @@ This source code maintained and managed by [Iaroslav Omelianenko][9] [1]:http://www.cs.ucf.edu/~kstanley/neat.html [2]:http://eplex.cs.ucf.edu/neat_software/ [3]:http://eplex.cs.ucf.edu/hyperNEATpage/HyperNEAT.html -[4]:http://eplex.cs.ucf.edu/papers/stanley_alife09.pdf -[5]:https://www.mitpressjournals.org/doi/pdfplus/10.1162/ARTL_a_00071 -[6]:http://nn.cs.utexas.edu/keyword?stanley:phd04 -[7]:http://eplex.cs.ucf.edu/papers/stanley_gpem07.pdf -[8]:https://github.com/yaricom/goNEAT +[4]:https://doi.org/10.1162/artl.2009.15.2.15202 +[5]:https://doi.org/10.1162/ARTL_a_00071 +[6]:https://nn.cs.utexas.edu/downloads/papers/stanley.phd04.pdf +[7]:https://doi.org/10.1007/s10710-007-9028-8 +[8]:https://doi.org/10.5281/zenodo.13628842 [9]:https://io42.space \ No newline at end of file diff --git a/cppn/common_test.go b/cppn/common_test.go new file mode 100644 index 0000000..51bac62 --- /dev/null +++ b/cppn/common_test.go @@ -0,0 +1,31 @@ +package cppn + +import ( + "bytes" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/yaricom/goNEAT/v4/neat/network" + "testing" +) + +func checkNetworkSolverOutputs(solver network.Solver, outExpected []float64, delta float64, t *testing.T) { + signals := []float64{0.9, 5.2, 1.2, 0.6} + err := solver.LoadSensors(signals) + require.NoError(t, err, "failed to load sensors") + + res, err := solver.RecursiveSteps() + require.NoError(t, err, "failed to perform recursive activation") + require.True(t, res, "failed to relax network") + + outs := solver.ReadOutputs() + for i, out := range outs { + assert.InDelta(t, outExpected[i], out, delta, "wrong output at: %d", i) + } +} + +func printGraph(graph SubstrateGraphBuilder, t *testing.T) { + var buf bytes.Buffer + err := graph.Marshal(&buf) + assert.NoError(t, err) + t.Log(buf.String()) +} diff --git a/cppn/cpnn_test.go b/cppn/cpnn_test.go deleted file mode 100644 index 74c2c05..0000000 --- a/cppn/cpnn_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package cppn - -import ( - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "testing" -) - -const ( - cppnHyperNEATTestGenomePath = "../data/test/test_cppn_hyperneat_genome.yml" - cppnLeoHyperNEATTestGenomePath = "../data/test/test_cppn_leo_hyperneat_genome.yml" -) - -func TestQuadNode_NodeVariance(t *testing.T) { - root := buildTree() - - // get variance and check results - variance := nodeVariance(root) - assert.InDelta(t, 3.3877551020408165, variance, 1e-16) -} - -func TestQuadNode_nodeCPPNValues(t *testing.T) { - root := buildTree() - - // get CPPN values and test results - vals := nodeCPPNValues(root) - require.Len(t, vals, 7, "wrong node values length") - - expected := []float64{0, 1, 2, 3, 2, 4, 6} - assert.ElementsMatch(t, expected, vals) -} - -func TestReadCPPFromGenomeFile(t *testing.T) { - cppn, err := ReadCPPFromGenomeFile(cppnHyperNEATTestGenomePath) - require.NoError(t, err, "failed to read genome file") - require.NotNil(t, cppn, "CPPN expected") - require.Equal(t, cppn.NodeCount(), 7, "wrong nodes number") - require.Equal(t, cppn.LinkCount(), 7, "wrong links number") - - // test query - coords := []float64{0.0, 0.0, 0.5, 0.5} - outs, err := queryCPPN(coords, cppn) - require.NoError(t, err, "failed to query CPPN") - require.NotNil(t, outs, "output expected") - require.Len(t, outs, 1) - assert.InDelta(t, 0.4864161653290716, outs[0], 1e-16, "wrong output value") -} - -func buildTree() *QuadNode { - root := NewQuadNode(0, 0, 1, 1) - root.Nodes = []*QuadNode{ - NewQuadNode(-1, 1, 0.5, 2), - NewQuadNode(-1, -1, 0.5, 2), - NewQuadNode(1, 1, 0.5, 2), - NewQuadNode(1, -1, 0.5, 2), - } - fillW(root.Nodes, 2.0) - - root.Nodes[0].Nodes = []*QuadNode{ - NewQuadNode(-1, 1, 0.5, 3), - NewQuadNode(-1, -1, 0.5, 3), - NewQuadNode(1, 1, 0.5, 3), - NewQuadNode(1, -1, 0.5, 3), - } - fillW(root.Nodes[0].Nodes, 1.0) - return root -} - -func fillW(nodes []*QuadNode, factor float64) { - for i, n := range nodes { - n.W = float64(i) * factor - } -} diff --git a/cppn/cppn.go b/cppn/cppn.go index a199b2c..3157d0a 100644 --- a/cppn/cppn.go +++ b/cppn/cppn.go @@ -1,27 +1,34 @@ -// The package CPPN provides implementation of Compositional Pattern Producing Network +// Package cppn provides implementation of Compositional Pattern Producing Network // which is a part of Hypercube-based NEAT algorithm implementation package cppn import ( "errors" - "github.com/yaricom/goNEAT/neat/genetics" - "github.com/yaricom/goNEAT/neat/network" + "github.com/yaricom/goNEAT/v4/neat/genetics" + "github.com/yaricom/goNEAT/v4/neat/network" + "gonum.org/v1/gonum/stat" "math" - "os" ) -// Reads CPPN from specified genome and creates network solver -func ReadCPPFromGenomeFile(genomePath string) (network.NetworkSolver, error) { - if genomeFile, err := os.Open(genomePath); err != nil { +// FastSolverFromGenomeFile Reads CPPN from specified genome and creates network solver +func FastSolverFromGenomeFile(genomePath string) (network.Solver, error) { + if net, err := NetworkFromGenomeFile(genomePath); err != nil { return nil, err - } else if r, err := genetics.NewGenomeReader(genomeFile, genetics.YAMLGenomeEncoding); err != nil { + } else { + return net, nil + } +} + +// NetworkFromGenomeFile Reads CPPN from specified genome and creates phenotype network +func NetworkFromGenomeFile(genomePath string) (*network.Network, error) { + if reader, err := genetics.NewGenomeReaderFromFile(genomePath); err != nil { return nil, err - } else if genome, err := r.Read(); err != nil { + } else if genome, err := reader.Read(); err != nil { return nil, err } else if net, err := genome.Genesis(genome.Id); err != nil { return nil, err } else { - return net.FastNetworkSolver() + return net, nil } } @@ -33,9 +40,9 @@ func createThresholdNormalizedLink(cppnOutput float64, srcIndex, dstIndex int, l weight *= -1 // restore sign } link := network.FastNetworkLink{ - Weight: weight, - SourceIndx: srcIndex, - TargetIndx: dstIndex, + Weight: weight, + SourceIndex: srcIndex, + TargetIndex: dstIndex, } return &link } @@ -45,15 +52,15 @@ func createLink(cppnOutput float64, srcIndex, dstIndex int, weightRange float64) weight := cppnOutput weight *= weightRange // scale to fit given weight range link := network.FastNetworkLink{ - Weight: weight, - SourceIndx: srcIndex, - TargetIndx: dstIndex, + Weight: weight, + SourceIndex: srcIndex, + TargetIndex: dstIndex, } return &link } // Calculates outputs of provided CPPN network solver with given hypercube coordinates. -func queryCPPN(coordinates []float64, cppn network.NetworkSolver) ([]float64, error) { +func queryCPPN(coordinates []float64, cppn network.Solver) ([]float64, error) { // flush networks activation from previous run if res, err := cppn.Flush(); err != nil { return nil, err @@ -74,6 +81,36 @@ func queryCPPN(coordinates []float64, cppn network.NetworkSolver) ([]float64, er return cppn.ReadOutputs(), nil } +//func queryCPPNNetwork(coordinates []float64, cppn *network.Network) ([]float64, error) { +// if res, err := cppn.Flush(); err != nil { +// return nil, err +// } else if !res { +// return nil, errors.New("failed to flush CPPN network") +// } +// +// // load inputs +// if err := cppn.LoadSensors(coordinates); err != nil { +// return nil, err +// } +// +// // do activations +// maxDepth, err := cppn.MaxActivationDepth() +// if err != nil { +// neat.WarnLog("Failed to calculate max activation depth for CPPN network") +// } +// if maxDepth == 0 { +// neat.WarnLog("Max activation depth for CPPN network is zero, setting default value") +// maxDepth = 100 +// } +// if res, err := cppn.ForwardSteps(maxDepth); err != nil { +// return nil, err +// } else if !res { +// return nil, errors.New("failed to relax CPPN network recursively") +// } +// +// return cppn.ReadOutputs(), nil +//} + // Determines variance among CPPN values for certain hypercube region around specified node. // This variance is a heuristic indicator of the heterogeneity (i.e. presence of information) of a region. func nodeVariance(node *QuadNode) float64 { @@ -82,20 +119,9 @@ func nodeVariance(node *QuadNode) float64 { return 0.0 } - cppnVals := nodeCPPNValues(node) - // calculate median and variance - m, v := 0.0, 0.0 - for _, f := range cppnVals { - m += f - } - m /= float64(len(cppnVals)) - - for _, f := range cppnVals { - v += math.Pow(f-m, 2) - } - v /= float64(len(cppnVals)) - - return v + // node always has four child nodes if any + cppnValues := []float64{node.Nodes[0].Weight(), node.Nodes[1].Weight(), node.Nodes[2].Weight(), node.Nodes[3].Weight()} + return stat.Variance(cppnValues, nil) } // Collects the CPPN values stored in a given quadtree node @@ -105,11 +131,11 @@ func nodeCPPNValues(n *QuadNode) []float64 { accumulator := make([]float64, 0) for _, p := range n.Nodes { // go into child nodes - pVals := nodeCPPNValues(p) - accumulator = append(accumulator, pVals...) + cppnValues := nodeCPPNValues(p) + accumulator = append(accumulator, cppnValues...) } return accumulator } else { - return []float64{n.W} + return []float64{n.Weight()} } } diff --git a/cppn/cppn_test.go b/cppn/cppn_test.go new file mode 100644 index 0000000..ff16280 --- /dev/null +++ b/cppn/cppn_test.go @@ -0,0 +1,91 @@ +package cppn + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +const ( + cppnHyperNEATTestGenomePath = "../data/test/test_cppn_hyperneat_genome.yml" + cppnLeoHyperNEATTestGenomePath = "../data/test/test_cppn_leo_hyperneat_genome.yml" +) + +func TestQuadNode_NodeVariance(t *testing.T) { + root := buildTree() + + // get variance and check results + variance := nodeVariance(root) + assert.InDelta(t, 1.6666666666666667, variance, 1e-16) +} + +func TestQuadNode_nodeCPPNValues(t *testing.T) { + root := buildTree() + + // get CPPN values and test results + vals := nodeCPPNValues(root) + require.Len(t, vals, 7, "wrong node values length") + + expected := []float64{0, 1, 2, 3, 1, 2, 3} + assert.ElementsMatch(t, expected, vals) +} + +func TestFastSolverFromGenomeFile(t *testing.T) { + cppn, err := FastSolverFromGenomeFile(cppnHyperNEATTestGenomePath) + require.NoError(t, err, "failed to read genome file") + require.NotNil(t, cppn, "CPPN expected") + require.Equal(t, 9, cppn.NodeCount(), "wrong nodes number") + require.Equal(t, 8, cppn.LinkCount(), "wrong links number") + + // test query + coords := []float64{0.0, 0.0, 0.0, 0.5, 0.5, 0.0} + outs, err := queryCPPN(coords, cppn) + require.NoError(t, err, "failed to query CPPN") + require.NotNil(t, outs, "output expected") + require.Len(t, outs, 1) + assert.InDelta(t, 0.566, outs[0], 1e-3, "wrong output value") +} + +func TestFastSolverFromGenomeFile_LEO(t *testing.T) { + cppn, err := FastSolverFromGenomeFile(cppnLeoHyperNEATTestGenomePath) + require.NoError(t, err, "failed to read genome file") + require.NotNil(t, cppn, "CPPN expected") + require.Equal(t, 11, cppn.NodeCount(), "wrong nodes number") + require.Equal(t, 15, cppn.LinkCount(), "wrong links number") + + // test query + coords := []float64{0.0, 0.0, 0.0, 0.5, 0.5, 0.0} + outs, err := queryCPPN(coords, cppn) + require.NoError(t, err, "failed to query CPPN") + require.NotNil(t, outs, "output expected") + require.Len(t, outs, 2) + t.Log(outs) + assert.InDelta(t, 0.048, outs[0], 1e-3, "wrong output value") + assert.Equal(t, 1.0, outs[1], "wrong LEO value") +} + +func buildTree() *QuadNode { + root := NewQuadNode(0, 0, 1, 1, 1) + root.Nodes = []*QuadNode{ + NewQuadNode(-1, 1, 0.5, 0.5, 2), + NewQuadNode(-1, -1, 0.5, 0.5, 2), + NewQuadNode(1, 1, 0.5, 0.5, 2), + NewQuadNode(1, -1, 0.5, 0.5, 2), + } + fillW(root.Nodes, 1.0) + + root.Nodes[0].Nodes = []*QuadNode{ + NewQuadNode(-1, 1, 0.5, 0.5, 3), + NewQuadNode(-1, -1, 0.5, 0.5, 3), + NewQuadNode(1, 1, 0.5, 0.5, 3), + NewQuadNode(1, -1, 0.5, 0.5, 3), + } + fillW(root.Nodes[0].Nodes, 1.0) + return root +} + +func fillW(nodes []*QuadNode, factor float64) { + for i, n := range nodes { + n.CppnOut = []float64{float64(i) * factor} + } +} diff --git a/cppn/evolvable_substrate.go b/cppn/evolvable_substrate.go index ba25f20..8fdcf2f 100644 --- a/cppn/evolvable_substrate.go +++ b/cppn/evolvable_substrate.go @@ -2,44 +2,60 @@ package cppn import ( "container/list" - "github.com/yaricom/goESHyperNEAT/eshyperneat" - "math" - "fmt" - "github.com/yaricom/goNEAT/neat/network" - "github.com/yaricom/goNEAT/neat/utils" + "github.com/pkg/errors" + "github.com/yaricom/goESHyperNEAT/v2/eshyperneat" + neatmath "github.com/yaricom/goNEAT/v4/neat/math" + "github.com/yaricom/goNEAT/v4/neat/network" + "math" ) -// The evolvable substrate holds configuration of ANN produced by CPPN within hypecube where each 4-dimensional point +// EvolvableSubstrate The evolvable substrate holds configuration of ANN produced by CPPN within hypecube where each 4-dimensional point // mark connection weight between two ANN units. The topology of ANN is not rigid as in plain substrate and can be evolved // by introducing novel nodes to the ANN. This provides extra benefits that the topology of ANN should not be handcrafted // by human, but produced during substrate generation from controlling CPPN and nodes locations may be arbitrary that suits // the best for the task at hand. type EvolvableSubstrate struct { - // The layout of neuron nodes in this substrate + // Layout The layout of neuron nodes in this substrate Layout EvolvableSubstrateLayout - // The activation function's type for neurons encoded - NodesActivation utils.NodeActivationType + // HiddenNodesActivation The activation function type for hidden neurons encoded + HiddenNodesActivation neatmath.NodeActivationType + // OutputNodesActivation The activation function type for output neurons encoded + OutputNodesActivation neatmath.NodeActivationType // The CPPN network solver to describe geometry of substrate - cppn network.NetworkSolver + cppn *network.Network // The reusable coordinates buffer coords []float64 } -// Creates new instance of evolvable substrate -func NewEvolvableSubstrate(layout EvolvableSubstrateLayout, nodesActivation utils.NodeActivationType) *EvolvableSubstrate { +// NewEvolvableSubstrate Creates new instance of evolvable substrate +func NewEvolvableSubstrate(layout EvolvableSubstrateLayout, hiddenNodesActivation, outputNodesActivation neatmath.NodeActivationType) *EvolvableSubstrate { return &EvolvableSubstrate{ - coords: make([]float64, 4), - Layout: layout, - NodesActivation: nodesActivation, + coords: make([]float64, 6), + Layout: layout, + HiddenNodesActivation: hiddenNodesActivation, + OutputNodesActivation: outputNodesActivation, } } -// Creates network solver based on current substrate layout and provided Compositional Pattern Producing Network which +// NewEvolvableSubstrateWithBias creates new instance of evolvable substrate with defined cppnBias value. +// The cppnBias will be provided as first value of the CPPN inputs array. +func NewEvolvableSubstrateWithBias(layout EvolvableSubstrateLayout, hiddenNodesActivation, outputNodesActivation neatmath.NodeActivationType, cppnBias float64) *EvolvableSubstrate { + coords := make([]float64, 7) + coords[0] = cppnBias + return &EvolvableSubstrate{ + coords: coords, + Layout: layout, + HiddenNodesActivation: hiddenNodesActivation, + OutputNodesActivation: outputNodesActivation, + } +} + +// CreateNetworkSolver Creates network solver based on current substrate layout and provided Compositional Pattern Producing Network which // used to define connections between network nodes. Optional graph_builder can be provided to collect graph nodes and edges // of created network solver. With graph builder it is possible to save/load network configuration as well as visualize it. -func (es *EvolvableSubstrate) CreateNetworkSolver(cppn network.NetworkSolver, graphBuilder SubstrateGraphBuilder, context *eshyperneat.ESHyperNEATContext) (network.NetworkSolver, error) { +func (es *EvolvableSubstrate) CreateNetworkSolver(cppn *network.Network, graphBuilder SubstrateGraphBuilder, options *eshyperneat.Options) (network.Solver, error) { es.cppn = cppn // the network layers will be collected in order: bias, input, output, hidden @@ -47,24 +63,48 @@ func (es *EvolvableSubstrate) CreateNetworkSolver(cppn network.NetworkSolver, gr firstOutput := firstInput + es.Layout.InputCount() firstHidden := firstOutput + es.Layout.OutputCount() - connections := make([]*network.FastNetworkLink, 0) - // The map to hold already created connections + links := make([]*network.FastNetworkLink, 0) + // The map to hold already created links connMap := make(map[string]*network.FastNetworkLink) - // The function to add new connection if appropriate - addConnection := func(weight float64, source, target int) (*network.FastNetworkLink, bool) { + // The function to add new link to the network if appropriate + addLink := func(qp *QuadPoint, source, target int) (*network.FastNetworkLink, bool) { key := fmt.Sprintf("%d_%d", source, target) if _, ok := connMap[key]; ok { - // connection already excists + // connection already exists + return nil, false + } + var link *network.FastNetworkLink + if options.LeoEnabled && qp.Leo > 0 { + link = createLink(qp.Weight, source, target, options.WeightRange) + } else if !options.LeoEnabled && math.Abs(qp.Weight) >= options.LinkThreshold { + // add only connections with signal exceeding provided threshold + link = createThresholdNormalizedLink(qp.Weight, source, target, options.LinkThreshold, options.WeightRange) + } + if link != nil { + links = append(links, link) + connMap[key] = link + return link, true + } else { return nil, false } - link := createLink(weight, source, target, context.WeightRange) - connections = append(connections, link) - connMap[key] = link - return link, true } - // Build connections from input nodes to the hidden nodes + // inline function to find activation type for a given neuron + activationForNeuron := func(nodeIndex int) neatmath.NodeActivationType { + if nodeIndex < firstOutput { + // input nodes + return neatmath.LinearActivation + } else if nodeIndex < firstHidden { + // output nodes activations + return es.OutputNodesActivation + } else { + // hidden nodes activation + return es.HiddenNodesActivation + } + } + + // Build links from input nodes to the hidden nodes var root *QuadNode for in := firstInput; in < firstOutput; in++ { // Analyse outgoing connectivity pattern from this input @@ -73,41 +113,28 @@ func (es *EvolvableSubstrate) CreateNetworkSolver(cppn network.NetworkSolver, gr return nil, err } // add input node to graph - if _, err := addNodeToBuilder(graphBuilder, in, network.InputNeuron, utils.NullActivation, input); err != nil { + if _, err = addNodeToBuilder(graphBuilder, in, network.InputNeuron, activationForNeuron(in), input); err != nil { return nil, err } - if root, err = es.quadTreeDivideAndInit(input.X, input.Y, true, context); err != nil { + if root, err = es.quadTreeDivideAndInit(input.X, input.Y, input.Z, true, options); err != nil { return nil, err } qPoints := make([]*QuadPoint, 0) - if qPoints, err = es.pruneAndExpress(input.X, input.Y, qPoints, root, true, context); err != nil { + if qPoints, err = es.pruneAndExpress(input.X, input.Y, input.Z, qPoints, root, true, options); err != nil { return nil, err } - // iterate over quad points and add nodes/connections + // iterate over quad points and add nodes/links for _, qp := range qPoints { - nodePoint := NewPointF(qp.X2, qp.Y2) - targetIndex := es.Layout.IndexOfHidden(nodePoint) - if targetIndex == -1 { - // add hidden node to the substrate layout - if targetIndex, err = es.Layout.AddHiddenNode(nodePoint); err != nil { - return nil, err - } - - targetIndex += firstHidden // adjust index to the global indexes space - // add a node to the graph - if _, err := addNodeToBuilder(graphBuilder, targetIndex, network.HiddenNeuron, es.NodesActivation, nodePoint); err != nil { - return nil, err - } - - } else { - // adjust index to the global indexes space - targetIndex += firstHidden + // add hidden node to the substrate layout if needed + targetIndex, err := es.addHiddenNode(qp, firstHidden, graphBuilder) + if err != nil { + return nil, err } // add connection - if link, ok := addConnection(qp.Value, in, targetIndex); ok { + if link, ok := addLink(qp, in, targetIndex); ok { // add an edge to the graph - if _, err := addEdgeToBuilder(graphBuilder, in, targetIndex, link.Weight); err != nil { + if _, err = addEdgeToBuilder(graphBuilder, in, targetIndex, link.Weight); err != nil { return nil, err } } @@ -117,43 +144,31 @@ func (es *EvolvableSubstrate) CreateNetworkSolver(cppn network.NetworkSolver, gr // Build more hidden nodes into unexplored area through a number of iterations firstHiddenIter := firstHidden lastHidden := firstHiddenIter + es.Layout.HiddenCount() - for step := 0; step < context.ESIterations; step++ { + for step := 0; step < options.ESIterations; step++ { for hi := firstHiddenIter; hi < lastHidden; hi++ { // Analyse outgoing connectivity pattern from this hidden node hidden, err := es.Layout.NodePosition(hi-firstHidden, network.HiddenNeuron) if err != nil { return nil, err } - if root, err = es.quadTreeDivideAndInit(hidden.X, hidden.Y, true, context); err != nil { + if root, err = es.quadTreeDivideAndInit(hidden.X, hidden.Y, hidden.Z, true, options); err != nil { return nil, err } qPoints := make([]*QuadPoint, 0) - if qPoints, err = es.pruneAndExpress(hidden.X, hidden.Y, qPoints, root, true, context); err != nil { + if qPoints, err = es.pruneAndExpress(hidden.X, hidden.Y, hidden.Z, qPoints, root, true, options); err != nil { return nil, err } - // iterate over quad points and add nodes/connections + // iterate over quad points and add nodes/links for _, qp := range qPoints { - nodePoint := NewPointF(qp.X2, qp.Y2) - targetIndex := es.Layout.IndexOfHidden(nodePoint) - if targetIndex == -1 { - // add hidden node to the substrate layout - if targetIndex, err = es.Layout.AddHiddenNode(nodePoint); err != nil { - return nil, err - } - - targetIndex += firstHidden // adjust index to the global indexes space - // add a node to the graph - if _, err := addNodeToBuilder(graphBuilder, targetIndex, network.HiddenNeuron, es.NodesActivation, nodePoint); err != nil { - return nil, err - } - } else { - // adjust index to the global indexes space - targetIndex += firstHidden + // add hidden node to the substrate layout if needed + targetIndex, err := es.addHiddenNode(qp, firstHidden, graphBuilder) + if err != nil { + return nil, err } // add connection - if link, ok := addConnection(qp.Value, hi, targetIndex); ok { + if link, ok := addLink(qp, hi, targetIndex); ok { // add an edge to the graph - if _, err := addEdgeToBuilder(graphBuilder, hi, targetIndex, link.Weight); err != nil { + if _, err = addEdgeToBuilder(graphBuilder, hi, targetIndex, link.Weight); err != nil { return nil, err } } @@ -173,19 +188,19 @@ func (es *EvolvableSubstrate) CreateNetworkSolver(cppn network.NetworkSolver, gr return nil, err } // add output node to graph - if _, err := addNodeToBuilder(graphBuilder, oi, network.OutputNeuron, es.NodesActivation, output); err != nil { + if _, err = addNodeToBuilder(graphBuilder, oi, network.OutputNeuron, activationForNeuron(oi), output); err != nil { return nil, err } - if root, err = es.quadTreeDivideAndInit(output.X, output.Y, false, context); err != nil { + if root, err = es.quadTreeDivideAndInit(output.X, output.Y, output.Z, false, options); err != nil { return nil, err } qPoints := make([]*QuadPoint, 0) - if qPoints, err = es.pruneAndExpress(output.X, output.Y, qPoints, root, false, context); err != nil { + if qPoints, err = es.pruneAndExpress(output.X, output.Y, output.Z, qPoints, root, false, options); err != nil { return nil, err } - // iterate over quad points and add nodes/connections where appropriate + // iterate over quad points and add nodes/links where appropriate for _, qp := range qPoints { nodePoint := NewPointF(qp.X1, qp.Y1) sourceIndex := es.Layout.IndexOfHidden(nodePoint) @@ -194,9 +209,9 @@ func (es *EvolvableSubstrate) CreateNetworkSolver(cppn network.NetworkSolver, gr sourceIndex += firstHidden // adjust index to the global indexes space // add connection - if link, ok := addConnection(qp.Value, sourceIndex, oi); ok { + if link, ok := addLink(qp, sourceIndex, oi); ok { // add an edge to the graph - if _, err := addEdgeToBuilder(graphBuilder, sourceIndex, oi, link.Weight); err != nil { + if _, err = addEdgeToBuilder(graphBuilder, sourceIndex, oi, link.Weight); err != nil { return nil, err } } @@ -207,30 +222,49 @@ func (es *EvolvableSubstrate) CreateNetworkSolver(cppn network.NetworkSolver, gr totalNeuronCount := es.Layout.InputCount() + es.Layout.OutputCount() + es.Layout.HiddenCount() // build activations - activations := make([]utils.NodeActivationType, totalNeuronCount) + activations := make([]neatmath.NodeActivationType, totalNeuronCount) for i := 0; i < totalNeuronCount; i++ { - if i < firstOutput { - // input nodes - NULL activation - activations[i] = utils.NullActivation - } else { - // hidden/output nodes - defined activation - activations[i] = es.NodesActivation - } - + activations[i] = activationForNeuron(i) } // create fast network solver + if totalNeuronCount == 0 || len(activations) != totalNeuronCount { + message := fmt.Sprintf("failed to create network solver: links [%d], nodes [%d], activations [%d], LEO [%t]", + len(links), totalNeuronCount, len(activations), options.LeoEnabled) + return nil, errors.New(message) + } solver := network.NewFastModularNetworkSolver( 0, es.Layout.InputCount(), es.Layout.OutputCount(), totalNeuronCount, - activations, connections, nil, nil) // No BIAS + activations, links, nil, nil) // No BIAS return solver, nil } -// Divides and initialize the quadtree from provided coordinates of source (outgoing = true) or target node (outgoing = false) at (a, b). -// Returns quadtree, in which each quadnode at (x,y) stores CPPN activation level for its position. The initialized +func (es *EvolvableSubstrate) addHiddenNode(qp *QuadPoint, firstHidden int, graphBuilder SubstrateGraphBuilder) (targetIndex int, err error) { + nodePoint := NewPointF(qp.X2, qp.Y2) + targetIndex = es.Layout.IndexOfHidden(nodePoint) + if targetIndex == -1 { + // add hidden node to the substrate layout + if targetIndex, err = es.Layout.AddHiddenNode(nodePoint); err != nil { + return -1, err + } + + targetIndex += firstHidden // adjust index to the global indexes space + // add a node to the graph + if _, err = addNodeToBuilder(graphBuilder, targetIndex, network.HiddenNeuron, es.HiddenNodesActivation, nodePoint); err != nil { + return -1, err + } + } else { + // adjust index to the global indexes space + targetIndex += firstHidden + } + return targetIndex, nil +} + +// Divides and initialize the quadtree from provided coordinates of source (outgoing = true) or target node (outgoing = false) at (a,b,c). +// Returns quadtree, in which each quadnode at (x,y,z) stores CPPN activation level for its position. The initialized // quadtree is used in the PruningAndExtraction phase to generate the actual ANN connections. -func (es *EvolvableSubstrate) quadTreeDivideAndInit(a, b float64, outgoing bool, context *eshyperneat.ESHyperNEATContext) (root *QuadNode, err error) { - root = NewQuadNode(0.0, 0.0, 1.0, 1) +func (es *EvolvableSubstrate) quadTreeDivideAndInit(a, b, c float64, outgoing bool, options *eshyperneat.Options) (root *QuadNode, err error) { + root = NewQuadNode(0.0, 0.0, options.Width, options.Height, 1) queue := list.New() queue.PushBack(root) @@ -239,23 +273,23 @@ func (es *EvolvableSubstrate) quadTreeDivideAndInit(a, b float64, outgoing bool, // de-queue p := queue.Remove(queue.Front()).(*QuadNode) - // Divide into sub-regions and assign children to parent + // Divide into subregions and assign children to parent p.Nodes = []*QuadNode{ - NewQuadNode(p.X-p.Width/2.0, p.Y-p.Width/2.0, p.Width/2.0, p.Level+1), - NewQuadNode(p.X-p.Width/2.0, p.Y+p.Width/2.0, p.Width/2.0, p.Level+1), - NewQuadNode(p.X+p.Width/2.0, p.Y-p.Width/2.0, p.Width/2.0, p.Level+1), - NewQuadNode(p.X+p.Width/2.0, p.Y+p.Width/2.0, p.Width/2.0, p.Level+1), + NewQuadNode(p.X-p.Width/2.0, p.Y-p.Height/2.0, p.Width/2.0, p.Height/2.0, p.Level+1), + NewQuadNode(p.X-p.Width/2.0, p.Y+p.Height/2.0, p.Width/2.0, p.Height/2.0, p.Level+1), + NewQuadNode(p.X+p.Width/2.0, p.Y-p.Height/2.0, p.Width/2.0, p.Height/2.0, p.Level+1), + NewQuadNode(p.X+p.Width/2.0, p.Y+p.Height/2.0, p.Width/2.0, p.Height/2.0, p.Level+1), } - for _, c := range p.Nodes { + for _, node := range p.Nodes { if outgoing { // Querying connection from input or hidden node (Outgoing connectivity pattern) - if c.W, err = es.queryCPPN(a, b, c.X, c.Y); err != nil { + if node.CppnOut, err = es.queryCPPN(a, b, c, node.X, node.Y, node.Z); err != nil { return nil, err } } else { // Querying connection to output node (Incoming connectivity pattern) - if c.W, err = es.queryCPPN(c.X, c.Y, a, b); err != nil { + if node.CppnOut, err = es.queryCPPN(node.X, node.Y, node.Z, a, b, c); err != nil { return nil, err } } @@ -263,9 +297,9 @@ func (es *EvolvableSubstrate) quadTreeDivideAndInit(a, b float64, outgoing bool, } // Divide until initial resolution or if variance is still high - if p.Level < context.InitialDepth || (p.Level < context.MaximalDepth && nodeVariance(p) > context.DivisionThreshold) { - for _, c := range p.Nodes { - queue.PushBack(c) + if p.Level < options.InitialDepth || (p.Level < options.MaximalDepth && nodeVariance(p) > options.DivisionThreshold) { + for _, child := range p.Nodes { + queue.PushBack(child) } } } @@ -274,10 +308,10 @@ func (es *EvolvableSubstrate) quadTreeDivideAndInit(a, b float64, outgoing bool, // Decides what regions should have higher neuron density based on variation and express new neurons and connections into // these regions. -// Receives coordinates of source (outgoing = true) or target node (outgoing = false) at (a, b) and initialized quadtree node. +// Receive coordinates of source (outgoing = true) or target node (outgoing = false) at (a, b) and initialized quadtree node. // Adds the connections that are in bands of the two-dimensional cross-section of the hypercube containing the source // or target node to the connections list and return modified list. -func (es *EvolvableSubstrate) pruneAndExpress(a, b float64, connections []*QuadPoint, node *QuadNode, outgoing bool, context *eshyperneat.ESHyperNEATContext) ([]*QuadPoint, error) { +func (es *EvolvableSubstrate) pruneAndExpress(a, b, c float64, connections []*QuadPoint, node *QuadNode, outgoing bool, options *eshyperneat.Options) ([]*QuadPoint, error) { // fast check if len(node.Nodes) == 0 { return connections, nil @@ -286,68 +320,70 @@ func (es *EvolvableSubstrate) pruneAndExpress(a, b float64, connections []*QuadP // Traverse quadtree depth-first until the current node’s variance is smaller than the variance threshold or // until the node has no children (which means that the variance is zero). left, right, top, bottom := 0.0, 0.0, 0.0, 0.0 - for _, c := range node.Nodes { - childVariance := nodeVariance(c) + for _, quadNode := range node.Nodes { + childVariance := nodeVariance(quadNode) - if childVariance >= context.VarianceThreshold { - if conn, err := es.pruneAndExpress(a, b, connections, c, outgoing, context); err != nil { + if childVariance >= options.VarianceThreshold { + if conn, err := es.pruneAndExpress(a, b, c, connections, quadNode, outgoing, options); err != nil { return nil, err } else { connections = append(connections, conn...) } - } else { - // Determine if point is in a band by checking neighbor CPPN values + } else if !options.LeoEnabled || (quadNode.Leo() > 0) { + // Band Pruning phase. + // If LEO is turned off this should always happen. + // If it is not it should only happen if the LEO output is greater than zero if outgoing { - if l, err := es.queryCPPN(a, b, c.X-node.Width, c.Y); err != nil { + if l, err := es.queryCPPN(a, b, c, quadNode.X-node.Width, quadNode.Y, quadNode.Z); err != nil { return nil, err } else { - left = math.Abs(c.W - l) + left = math.Abs(quadNode.Weight() - l[0]) } - if r, err := es.queryCPPN(a, b, c.X+node.Width, c.Y); err != nil { + if r, err := es.queryCPPN(a, b, c, quadNode.X+node.Width, quadNode.Y, quadNode.Z); err != nil { return nil, err } else { - right = math.Abs(c.W - r) + right = math.Abs(quadNode.Weight() - r[0]) } - if t, err := es.queryCPPN(a, b, c.X, c.Y-node.Width); err != nil { + if t, err := es.queryCPPN(a, b, c, quadNode.X, quadNode.Y-node.Height, quadNode.Z); err != nil { return nil, err } else { - top = math.Abs(c.W - t) + top = math.Abs(quadNode.Weight() - t[0]) } - if b, err := es.queryCPPN(a, b, c.X, c.Y+node.Width); err != nil { + if bot, err := es.queryCPPN(a, b, c, quadNode.X, quadNode.Y+node.Height, quadNode.Z); err != nil { return nil, err } else { - bottom = math.Abs(c.W - b) + bottom = math.Abs(quadNode.Weight() - bot[0]) } } else { - if l, err := es.queryCPPN(c.X-node.Width, c.Y, a, b); err != nil { + if l, err := es.queryCPPN(quadNode.X-node.Width, quadNode.Y, quadNode.Z, a, b, c); err != nil { return nil, err } else { - left = math.Abs(c.W - l) + left = math.Abs(quadNode.Weight() - l[0]) } - if r, err := es.queryCPPN(c.X+node.Width, c.Y, a, b); err != nil { + if r, err := es.queryCPPN(quadNode.X+node.Width, quadNode.Y, quadNode.Z, a, b, c); err != nil { return nil, err } else { - right = math.Abs(c.W - r) + right = math.Abs(quadNode.Weight() - r[0]) } - if t, err := es.queryCPPN(c.X, c.Y-node.Width, a, b); err != nil { + if t, err := es.queryCPPN(quadNode.X, quadNode.Y-node.Height, quadNode.Z, a, b, c); err != nil { return nil, err } else { - top = math.Abs(c.W - t) + top = math.Abs(quadNode.Weight() - t[0]) } - if b, err := es.queryCPPN(c.X, c.Y+node.Width, a, b); err != nil { + if bot, err := es.queryCPPN(quadNode.X, quadNode.Y+node.Height, quadNode.Z, a, b, c); err != nil { return nil, err } else { - bottom = math.Abs(c.W - b) + bottom = math.Abs(quadNode.Weight() - bot[0]) } } - if math.Max(math.Min(top, bottom), math.Min(left, right)) > context.BandingThreshold { - // Create new connection specified by QuadPoint(x1,y1,x2,y2,weight) in 4D hypercube + if math.Max(math.Min(top, bottom), math.Min(left, right)) > options.BandingThreshold { + // Create new connection specified by QuadPoint(x1,y1,z1,x2,y2,z2,weight) in 4D hypercube var conn *QuadPoint if outgoing { - conn = NewQuadPoint(a, b, c.X, c.Y, c.W) + conn = NewQuadPoint(a, b, c, quadNode.X, quadNode.Y, quadNode.Z, quadNode) } else { - conn = NewQuadPoint(c.X, c.Y, a, b, c.W) + conn = NewQuadPoint(quadNode.X, quadNode.Y, quadNode.Z, a, b, c, quadNode) } connections = append(connections, conn) @@ -360,15 +396,22 @@ func (es *EvolvableSubstrate) pruneAndExpress(a, b float64, connections []*QuadP // Query CPPN associated with this substrate for specified Hypercube coordinate and returns value produced or error if // operation failed -func (es *EvolvableSubstrate) queryCPPN(x1, y1, x2, y2 float64) (float64, error) { - es.coords[0] = x1 - es.coords[1] = y1 - es.coords[2] = x2 - es.coords[3] = y2 +func (es *EvolvableSubstrate) queryCPPN(x1, y1, z1, x2, y2, z2 float64) ([]float64, error) { + offset := 0 + if len(es.coords) == 7 { + // CPPN bias defined + offset = 1 + } + es.coords[offset] = x1 + es.coords[offset+1] = y1 + es.coords[offset+2] = z1 + es.coords[offset+3] = x2 + es.coords[offset+4] = y2 + es.coords[offset+5] = z2 if outs, err := queryCPPN(es.coords, es.cppn); err != nil { - return math.MaxFloat64, err + return nil, errors.Wrap(err, "failed to query CPPN") } else { - return outs[0], nil + return outs, nil } } diff --git a/cppn/evolvable_substrate_layout.go b/cppn/evolvable_substrate_layout.go index 2b62051..8a28d0f 100644 --- a/cppn/evolvable_substrate_layout.go +++ b/cppn/evolvable_substrate_layout.go @@ -2,31 +2,30 @@ package cppn import ( "fmt" - "github.com/pkg/errors" - "github.com/yaricom/goNEAT/neat/network" + "github.com/yaricom/goNEAT/v4/neat/network" ) -// Defines layout of neurons in the substrate +// EvolvableSubstrateLayout Defines layout of neurons in the substrate type EvolvableSubstrateLayout interface { - // Returns coordinates of the neuron with specified index [0; count) and type + // NodePosition Returns coordinates of the neuron with specified index [0; count) and type NodePosition(index int, nType network.NodeNeuronType) (*PointF, error) - // Adds new hidden node to the substrate + // AddHiddenNode Adds new hidden node to the substrate // Returns the index of added hidden neuron or error if failed. AddHiddenNode(position *PointF) (int, error) - // Returns index of hidden node at specified position or -1 if not fund + // IndexOfHidden Returns index of hidden node at specified position or -1 if not fund IndexOfHidden(position *PointF) int - // Returns number of INPUT neurons in the layout + // InputCount Returns number of INPUT neurons in the layout InputCount() int - // Returns number of HIDDEN neurons in the layout + // HiddenCount Returns number of HIDDEN neurons in the layout HiddenCount() int - // Returns number of OUTPUT neurons in the layout + // OutputCount Returns number of OUTPUT neurons in the layout OutputCount() int } -// Creates new instance with given input and output neurons count +// NewMappedEvolvableSubstrateLayout Creates new instance with given input and output neurons count func NewMappedEvolvableSubstrateLayout(inputCount, outputCount int) (*MappedEvolvableSubstrateLayout, error) { if inputCount == 0 { return nil, errors.New("the number of input neurons can not be ZERO") @@ -46,7 +45,8 @@ func NewMappedEvolvableSubstrateLayout(inputCount, outputCount int) (*MappedEvol return l, nil } -// The EvolvableSubstrateLayout implementation using map for binding between hidden node and its index +// MappedEvolvableSubstrateLayout the EvolvableSubstrateLayout implementation using map for binding between hidden +// node and its index type MappedEvolvableSubstrateLayout struct { // The map to hold binding between hidden node and its index for fast search hNodesMap map[PointF]int diff --git a/cppn/evolvable_substrate_layout_test.go b/cppn/evolvable_substrate_layout_test.go index 80d3f79..2a7d1cc 100644 --- a/cppn/evolvable_substrate_layout_test.go +++ b/cppn/evolvable_substrate_layout_test.go @@ -3,7 +3,7 @@ package cppn import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/yaricom/goNEAT/neat/network" + "github.com/yaricom/goNEAT/v4/neat/network" "testing" ) diff --git a/cppn/evolvable_substrate_test.go b/cppn/evolvable_substrate_test.go index 408f27d..dac4d16 100644 --- a/cppn/evolvable_substrate_test.go +++ b/cppn/evolvable_substrate_test.go @@ -3,9 +3,8 @@ package cppn import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/yaricom/goESHyperNEAT/eshyperneat" - "github.com/yaricom/goNEAT/neat/utils" - "os" + "github.com/yaricom/goESHyperNEAT/v2/eshyperneat" + "github.com/yaricom/goNEAT/v4/neat/math" "testing" ) @@ -16,48 +15,61 @@ func TestEvolvableSubstrate_CreateNetworkSolver(t *testing.T) { layout, err := NewMappedEvolvableSubstrateLayout(inputCount, outputCount) require.NoError(t, err, "failed to create layout") - substr := NewEvolvableSubstrate(layout, utils.SigmoidSteepenedActivation) + substr := NewEvolvableSubstrate(layout, math.SigmoidSteepenedActivation, math.LinearActivation) - cppn, err := ReadCPPFromGenomeFile(cppnHyperNEATTestGenomePath) + cppn, err := NetworkFromGenomeFile(cppnHyperNEATTestGenomePath) require.NoError(t, err, "failed to read CPPN") - context, err := loadESHyperNeatContext(esHyperNeatTestConfigFile) + context, err := loadESHyperNeatOptions(esHyperNeatTestConfigFile) require.NoError(t, err, "failed to read ESHyperNEAT context") // test solver creation - graph := NewSubstrateGraphMLBuilder("", false) + graph := NewSubstrateGraphMLBuilder("TestEvolvableSubstrate_CreateNetworkSolver", false) solver, err := substr.CreateNetworkSolver(cppn, graph, context) require.NoError(t, err, "failed to create solver") - //var buf bytes.Buffer - //err = graph.Marshal(&buf) - //t.Log(buf.String()) + printGraph(graph, t) totalNodeCount := inputCount + outputCount + layout.HiddenCount() assert.Equal(t, totalNodeCount, solver.NodeCount(), "wrong total node count") - assert.Equal(t, 10, solver.LinkCount(), "wrong link number") - - // test outputs - signals := []float64{0.9, 5.2, 1.2, 0.6} - err = solver.LoadSensors(signals) - assert.NoError(t, err, "failed to load sensors") - - res, err := solver.RecursiveSteps() - require.NoError(t, err, "failed to propagate recursive activation") - require.True(t, res, "failed to relax network") - - outs := solver.ReadOutputs() - outExpected := []float64{0.5, 0.5} - delta := 0.0000000001 - for i, out := range outs { - assert.InDelta(t, outExpected[i], out, delta, "unexpected output: %v at: %d", out, i) - } + assert.Equal(t, 27, solver.LinkCount(), "wrong link number") + + // check outputs + outExpected := []float64{0, 0} + checkNetworkSolverOutputs(solver, outExpected, 0.0, t) } -// Loads ES-HyperNeat context from provided config file's path -func loadESHyperNeatContext(configPath string) (*eshyperneat.ESHyperNEATContext, error) { - if r, err := os.Open(configPath); err != nil { - return nil, err - } else if ctx, err := eshyperneat.Load(r); err != nil { +func TestEvolvableSubstrate_CreateNetworkSolver_LEO(t *testing.T) { + inputCount, outputCount := 4, 2 + layout, err := NewMappedEvolvableSubstrateLayout(inputCount, outputCount) + require.NoError(t, err, "failed to create layout") + + substr := NewEvolvableSubstrate(layout, math.SigmoidSteepenedActivation, math.LinearActivation) + + cppn, err := NetworkFromGenomeFile(cppnLeoHyperNEATTestGenomePath) + require.NoError(t, err, "failed to read CPPN") + context, err := loadESHyperNeatOptions(esHyperNeatTestConfigFile) + require.NoError(t, err, "failed to read ESHyperNEAT context") + context.LeoEnabled = true + + // test solver creation + graph := NewSubstrateGraphMLBuilder("TestEvolvableSubstrate_CreateNetworkSolver", false) + solver, err := substr.CreateNetworkSolver(cppn, graph, context) + require.NoError(t, err, "failed to create solver") + + printGraph(graph, t) + + totalNodeCount := inputCount + outputCount + layout.HiddenCount() + assert.Equal(t, totalNodeCount, solver.NodeCount(), "wrong total node count") + assert.Equal(t, 5, solver.LinkCount(), "wrong link number") + + // check outputs + outExpected := []float64{0, 0} + checkNetworkSolverOutputs(solver, outExpected, 0.0, t) +} + +// Loads ES-HyperNeat options from provided config file's path +func loadESHyperNeatOptions(configPath string) (*eshyperneat.Options, error) { + if ctx, err := eshyperneat.LoadYAMLConfigFile(configPath); err != nil { return nil, err } else { return ctx, nil diff --git a/cppn/quad_tree.go b/cppn/quad_tree.go index f78578e..33d480e 100644 --- a/cppn/quad_tree.go +++ b/cppn/quad_tree.go @@ -2,9 +2,9 @@ package cppn import "fmt" -// Defines point with float precision coordinates +// PointF Defines point with float precision coordinates type PointF struct { - X, Y float64 + X, Y, Z float64 } func NewPointF(x, y float64) *PointF { @@ -12,35 +12,49 @@ func NewPointF(x, y float64) *PointF { } func (p *PointF) String() string { - return fmt.Sprintf("(%f, %f)", p.X, p.Y) + return fmt.Sprintf("(%f, %f, %f)", p.X, p.Y, p.Z) } -// Defines the quad-point in the 4 dimensional hypercube +// QuadPoint Defines the quad-point in the 6 dimensional hypercube type QuadPoint struct { // The associated coordinates - X1, X2, Y1, Y2 float64 - // The value for this point - Value float64 + X1, X2, Y1, Y2, Z1, Z2 float64 + // Weight + Weight float64 + // Leo + Leo float64 } func (q *QuadPoint) String() string { - return fmt.Sprintf("((%f, %f),(%f, %f)) = %f", q.X1, q.Y1, q.X2, q.Y2, q.Value) + str := fmt.Sprintf("((%f, %f, %f),(%f, %f, %f)) = %f", q.X1, q.Y1, q.Z1, q.X2, q.Y2, q.Z2, q.Weight) + if q.Leo >= 0 { + var status string + if q.Leo > 0 { + status = "enabled" + } else { + status = "disabled" + } + str = fmt.Sprintf("%s [%s]", str, status) + } + return str } -// Creates new quad point -func NewQuadPoint(x1, y1, x2, y2, value float64) *QuadPoint { - return &QuadPoint{X1: x1, Y1: y1, X2: x2, Y2: y2, Value: value} +// NewQuadPoint Creates new quad point +func NewQuadPoint(x1, y1, z1, x2, y2, z2 float64, node *QuadNode) *QuadPoint { + return &QuadPoint{X1: x1, Y1: y1, Z1: z1, X2: x2, Y2: y2, Z2: z2, Weight: node.Weight(), Leo: node.Leo()} } -// Defines quad-tree node to model 4 dimensional hypercube +// QuadNode Defines quad-tree node to model 4 dimensional hypercube type QuadNode struct { // The coordinates of center of this quad-tree node's square - X, Y float64 + X, Y, Z float64 // The width of this quad-tree node's square Width float64 + // The height of this quad-tree node's square + Height float64 - // The CPPN activation level for this node - W float64 + // The CPPN outputs for this node + CppnOut []float64 // The level of this node in the quad-tree Level int @@ -48,18 +62,49 @@ type QuadNode struct { Nodes []*QuadNode } +func (q *QuadNode) Weight() float64 { + return q.CppnOut[0] +} + +func (q *QuadNode) Leo() float64 { + if len(q.CppnOut) > 1 { + return q.CppnOut[1] + } + // if no LEO output - enable link unconditionally + return 1.0 +} + +func (q *QuadNode) HasLeo() bool { + return len(q.CppnOut) > 1 +} + func (q *QuadNode) String() string { - return fmt.Sprintf("((%f, %f), %f) = %f at %d", q.X, q.Y, q.Width, q.W, q.Level) + return fmt.Sprintf("((%f, %f, %f), %f x %f) = %f at %d", q.X, q.Y, q.Z, q.Width, q.Height, q.CppnOut, q.Level) +} + +// NewQuadNode Creates new quad-node with given parameters +func NewQuadNode(x, y, width, height float64, level int) *QuadNode { + node := QuadNode{ + X: x, + Y: y, + Width: width, + Height: height, + CppnOut: []float64{0.0}, + Level: level, + } + return &node } -// Creates new quad-node with given parameters -func NewQuadNode(x, y, width float64, level int) *QuadNode { +// NewQuadNodeZ Creates new quad-node with given parameters with Z coordinate value +func NewQuadNodeZ(x, y, z, width, height float64, level int) *QuadNode { node := QuadNode{ - X: x, - Y: y, - Width: width, - W: 0.0, - Level: level, + X: x, + Y: y, + Z: z, + Width: width, + Height: height, + CppnOut: []float64{0.0}, + Level: level, } return &node } diff --git a/cppn/substrate.go b/cppn/substrate.go index 64875e2..6a7841f 100644 --- a/cppn/substrate.go +++ b/cppn/substrate.go @@ -2,40 +2,43 @@ package cppn import ( "errors" + "fmt" + "github.com/yaricom/goESHyperNEAT/v2/hyperneat" + neatmath "github.com/yaricom/goNEAT/v4/neat/math" + "github.com/yaricom/goNEAT/v4/neat/network" "math" - - "github.com/yaricom/goESHyperNEAT/hyperneat" - "github.com/yaricom/goNEAT/neat/network" - "github.com/yaricom/goNEAT/neat/utils" ) -// Represents substrate holding configuration of ANN with weights produced by CPPN. According to HyperNEAT method +// Substrate represents substrate holding configuration of ANN with weights produced by CPPN. According to HyperNEAT method // the ANN neurons are encoded as coordinates in hypercube presented by this substrate. -// By default neurons will be placed into substrate within grid layout +// By default, neurons will be placed into substrate within grid layout type Substrate struct { - // The layout of neuron nodes in this substrate + // Layout The layout of neuron nodes in this substrate Layout SubstrateLayout - // The activation function's type for neurons encoded - NodesActivation utils.NodeActivationType + // HiddenNodesActivation The activation function's type for neurons encoded + HiddenNodesActivation neatmath.NodeActivationType + // OutputNodesActivation The activation function type for output neurons encoded + OutputNodesActivation neatmath.NodeActivationType } -// Creates new instance -func NewSubstrate(layout SubstrateLayout, nodesActivation utils.NodeActivationType) *Substrate { +// NewSubstrate creates new instance of substrate. +func NewSubstrate(layout SubstrateLayout, hiddenNodesActivation, outputNodesActivation neatmath.NodeActivationType) *Substrate { substr := Substrate{ - Layout: layout, - NodesActivation: nodesActivation, + Layout: layout, + HiddenNodesActivation: hiddenNodesActivation, + OutputNodesActivation: outputNodesActivation, } return &substr } -// Creates network solver based on current substrate layout and provided Compositional Pattern Producing Network which +// CreateNetworkSolver creates network solver based on current substrate layout and provided Compositional Pattern Producing Network which // used to define connections between network nodes. Optional graph_builder can be provided to collect graph nodes and edges // of created network solver. With graph builder it is possible to save/load network configuration as well as visualize it. -// If use_leo is True thar Link Expression Output extension to the HyperNEAT will be used instead of standard weight threshold +// If the useLeo is True thar Link Expression Output extension to the HyperNEAT will be used instead of standard weight threshold // technique of HyperNEAT to determine whether to express link between two nodes or not. With LEO the link expressed based // on value of additional output of the CPPN (if > 0 then expressed) -func (s *Substrate) CreateNetworkSolver(cppn network.NetworkSolver, use_leo bool, graph_builder SubstrateGraphBuilder, context *hyperneat.HyperNEATContext) (network.NetworkSolver, error) { +func (s *Substrate) CreateNetworkSolver(cppn network.Solver, useLeo bool, graphBuilder SubstrateGraphBuilder, options *hyperneat.Options) (network.Solver, error) { // check conditions if s.Layout.BiasCount() > 1 { return nil, errors.New("SUBSTRATE: maximum one BIAS node per network supported") @@ -50,34 +53,37 @@ func (s *Substrate) CreateNetworkSolver(cppn network.NetworkSolver, use_leo bool totalNeuronCount := lastHidden - connections := make([]*network.FastNetworkLink, 0) + links := make([]*network.FastNetworkLink, 0) biasList := make([]float64, totalNeuronCount) // inline function to find activation type for a given neuron - activationForNeuron := func(n_index int) utils.NodeActivationType { - if n_index < firstOutput { - // all bias and input neurons has null activation function associated because they actually has - // no inputs to be activated upon - return utils.NullActivation + activationForNeuron := func(nodeIndex int) neatmath.NodeActivationType { + if nodeIndex < firstOutput { + // input nodes + return neatmath.LinearActivation + } else if nodeIndex < firstHidden { + // output nodes activations + return s.OutputNodesActivation } else { - return s.NodesActivation + // hidden nodes activation + return s.HiddenNodesActivation } } // give bias inputs to all hidden and output nodes. - var link *network.FastNetworkLink - coord := make([]float64, 4) + coordinates := make([]float64, 6) for bi := firstBias; bi < firstInput; bi++ { // the bias coordinates - if b_coord, err := s.Layout.NodePosition(bi-firstBias, network.BiasNeuron); err != nil { + if biasPosition, err := s.Layout.NodePosition(bi-firstBias, network.BiasNeuron); err != nil { return nil, err } else { - coord[0] = b_coord.X - coord[1] = b_coord.Y + coordinates[0] = biasPosition.X + coordinates[1] = biasPosition.Y + coordinates[2] = biasPosition.Z // add bias node to builder - if _, err := addNodeToBuilder(graph_builder, bi, network.BiasNeuron, activationForNeuron(bi), b_coord); err != nil { + if _, err = addNodeToBuilder(graphBuilder, bi, network.BiasNeuron, activationForNeuron(bi), biasPosition); err != nil { return nil, err } } @@ -85,32 +91,25 @@ func (s *Substrate) CreateNetworkSolver(cppn network.NetworkSolver, use_leo bool // link the bias to all hidden nodes. for hi := firstHidden; hi < lastHidden; hi++ { // get hidden neuron coordinates - if h_coord, err := s.Layout.NodePosition(hi-firstHidden, network.HiddenNeuron); err != nil { + if hiddenPosition, err := s.Layout.NodePosition(hi-firstHidden, network.HiddenNeuron); err != nil { return nil, err } else { - coord[2] = h_coord.X - coord[3] = h_coord.Y + coordinates[2] = hiddenPosition.X + coordinates[3] = hiddenPosition.Y + coordinates[4] = hiddenPosition.Z // add node to graph - if _, err := addNodeToBuilder(graph_builder, hi, network.HiddenNeuron, activationForNeuron(hi), h_coord); err != nil { + if _, err = addNodeToBuilder(graphBuilder, hi, network.HiddenNeuron, activationForNeuron(hi), hiddenPosition); err != nil { return nil, err } } // find connection weight - link = nil - if outs, err := queryCPPN(coord, cppn); err != nil { + if link, err := fastNetworkLink(coordinates, cppn, useLeo, bi, hi, options); err != nil { return nil, err - } else if use_leo && outs[1] > 0 { - // add links only when CPPN's LEO output signals to - link = createLink(outs[0], bi, hi, context.WeightRange) - } else if !use_leo && math.Abs(outs[0]) > context.LinkThreshold { - // add only connections with signal exceeding provided threshold - link = createThresholdNormalizedLink(outs[0], bi, hi, context.LinkThreshold, context.WeightRange) - } - if link != nil { + } else if link != nil { biasList[hi] = link.Weight // add node and edge to graph - if _, err := addEdgeToBuilder(graph_builder, bi, hi, link.Weight); err != nil { + if _, err := addEdgeToBuilder(graphBuilder, bi, hi, link.Weight); err != nil { return nil, err } } @@ -119,32 +118,25 @@ func (s *Substrate) CreateNetworkSolver(cppn network.NetworkSolver, use_leo bool // link the bias to all output nodes for oi := firstOutput; oi < firstHidden; oi++ { // get output neuron coordinates - if o_coord, err := s.Layout.NodePosition(oi-firstOutput, network.OutputNeuron); err != nil { + if outputPosition, err := s.Layout.NodePosition(oi-firstOutput, network.OutputNeuron); err != nil { return nil, err } else { - coord[2] = o_coord.X - coord[3] = o_coord.Y + coordinates[2] = outputPosition.X + coordinates[3] = outputPosition.Y + coordinates[4] = outputPosition.Z // add node to graph - if _, err := addNodeToBuilder(graph_builder, oi, network.OutputNeuron, activationForNeuron(oi), o_coord); err != nil { + if _, err = addNodeToBuilder(graphBuilder, oi, network.OutputNeuron, activationForNeuron(oi), outputPosition); err != nil { return nil, err } } // find connection weight - link = nil - if outs, err := queryCPPN(coord, cppn); err != nil { + if link, err := fastNetworkLink(coordinates, cppn, useLeo, bi, oi, options); err != nil { return nil, err - } else if use_leo && outs[1] > 0 { - // add links only when CPPN's LEO output signals to - link = createLink(outs[0], bi, oi, context.WeightRange) - } else if !use_leo && math.Abs(outs[0]) > context.LinkThreshold { - // add only connections with signal exceeding provided threshold - link = createThresholdNormalizedLink(outs[0], bi, oi, context.LinkThreshold, context.WeightRange) - } - if link != nil { + } else if link != nil { biasList[oi] = link.Weight // add node and edge to graph - if _, err := addEdgeToBuilder(graph_builder, bi, oi, link.Weight); err != nil { + if _, err := addEdgeToBuilder(graphBuilder, bi, oi, link.Weight); err != nil { return nil, err } } @@ -155,41 +147,34 @@ func (s *Substrate) CreateNetworkSolver(cppn network.NetworkSolver, use_leo bool // link input nodes to hidden ones for in := firstInput; in < firstOutput; in++ { // get coordinates of input node - if i_coord, err := s.Layout.NodePosition(in-firstInput, network.InputNeuron); err != nil { + if inputPosition, err := s.Layout.NodePosition(in-firstInput, network.InputNeuron); err != nil { return nil, err } else { - coord[0] = i_coord.X - coord[1] = i_coord.Y + coordinates[0] = inputPosition.X + coordinates[1] = inputPosition.Y + coordinates[2] = inputPosition.Z // add node to graph - if _, err := addNodeToBuilder(graph_builder, in, network.InputNeuron, activationForNeuron(in), i_coord); err != nil { + if _, err = addNodeToBuilder(graphBuilder, in, network.InputNeuron, activationForNeuron(in), inputPosition); err != nil { return nil, err } } for hi := firstHidden; hi < lastHidden; hi++ { // get hidden neuron coordinates - if h_coord, err := s.Layout.NodePosition(hi-firstHidden, network.HiddenNeuron); err != nil { + if hiddenPosition, err := s.Layout.NodePosition(hi-firstHidden, network.HiddenNeuron); err != nil { return nil, err } else { - coord[2] = h_coord.X - coord[3] = h_coord.Y + coordinates[2] = hiddenPosition.X + coordinates[3] = hiddenPosition.Y + coordinates[4] = hiddenPosition.Z } // find connection weight - link = nil - if outs, err := queryCPPN(coord, cppn); err != nil { + if link, err := fastNetworkLink(coordinates, cppn, useLeo, in, hi, options); err != nil { return nil, err - } else if use_leo && outs[1] > 0 { - // add links only when CPPN's LEO output signals to - link = createLink(outs[0], in, hi, context.WeightRange) - } else if !use_leo && math.Abs(outs[0]) > context.LinkThreshold { - // add only connections with signal exceeding provided threshold - link = createThresholdNormalizedLink(outs[0], in, hi, context.LinkThreshold, context.WeightRange) - - } - if link != nil { - connections = append(connections, link) + } else if link != nil { + links = append(links, link) // add node and edge to graph - if _, err := addEdgeToBuilder(graph_builder, in, hi, link.Weight); err != nil { + if _, err := addEdgeToBuilder(graphBuilder, in, hi, link.Weight); err != nil { return nil, err } } @@ -198,35 +183,29 @@ func (s *Substrate) CreateNetworkSolver(cppn network.NetworkSolver, use_leo bool // link all hidden nodes to all output nodes. for hi := firstHidden; hi < lastHidden; hi++ { - if h_coord, err := s.Layout.NodePosition(hi-firstHidden, network.HiddenNeuron); err != nil { + if hiddenPosition, err := s.Layout.NodePosition(hi-firstHidden, network.HiddenNeuron); err != nil { return nil, err } else { - coord[0] = h_coord.X - coord[1] = h_coord.Y + coordinates[0] = hiddenPosition.X + coordinates[1] = hiddenPosition.Y + coordinates[2] = hiddenPosition.Z } for oi := firstOutput; oi < firstHidden; oi++ { // get output neuron coordinates - if o_coord, err := s.Layout.NodePosition(oi-firstOutput, network.OutputNeuron); err != nil { + if outputPosition, err := s.Layout.NodePosition(oi-firstOutput, network.OutputNeuron); err != nil { return nil, err } else { - coord[2] = o_coord.X - coord[3] = o_coord.Y + coordinates[2] = outputPosition.X + coordinates[3] = outputPosition.Y + coordinates[4] = outputPosition.Z } // find connection weight - link = nil - if outs, err := queryCPPN(coord, cppn); err != nil { + if link, err := fastNetworkLink(coordinates, cppn, useLeo, hi, oi, options); err != nil { return nil, err - } else if use_leo && outs[1] > 0 { - // add links only when CPPN's LEO output signals to - link = createLink(outs[0], hi, oi, context.WeightRange) - } else if !use_leo && math.Abs(outs[0]) > context.LinkThreshold { - // add only connections with signal exceeding provided threshold - link = createThresholdNormalizedLink(outs[0], hi, oi, context.LinkThreshold, context.WeightRange) - } - if link != nil { - connections = append(connections, link) + } else if link != nil { + links = append(links, link) // add node and edge to graph - if _, err := addEdgeToBuilder(graph_builder, hi, oi, link.Weight); err != nil { + if _, err := addEdgeToBuilder(graphBuilder, hi, oi, link.Weight); err != nil { return nil, err } } @@ -236,41 +215,34 @@ func (s *Substrate) CreateNetworkSolver(cppn network.NetworkSolver, use_leo bool // connect all input nodes directly to all output nodes for in := firstInput; in < firstOutput; in++ { // get coordinates of input node - if i_coord, err := s.Layout.NodePosition(in-firstInput, network.InputNeuron); err != nil { + if inputPosition, err := s.Layout.NodePosition(in-firstInput, network.InputNeuron); err != nil { return nil, err } else { - coord[0] = i_coord.X - coord[1] = i_coord.Y + coordinates[0] = inputPosition.X + coordinates[1] = inputPosition.Y + coordinates[2] = inputPosition.Z // add node to graph - if _, err := addNodeToBuilder(graph_builder, in, network.InputNeuron, activationForNeuron(in), i_coord); err != nil { + if _, err = addNodeToBuilder(graphBuilder, in, network.InputNeuron, activationForNeuron(in), inputPosition); err != nil { return nil, err } } for oi := firstOutput; oi < firstHidden; oi++ { // get output neuron coordinates - if o_coord, err := s.Layout.NodePosition(oi-firstOutput, network.OutputNeuron); err != nil { + if outputPosition, err := s.Layout.NodePosition(oi-firstOutput, network.OutputNeuron); err != nil { return nil, err } else { - coord[2] = o_coord.X - coord[3] = o_coord.Y + coordinates[2] = outputPosition.X + coordinates[3] = outputPosition.Y + coordinates[4] = outputPosition.Z } // find connection weight - link = nil - if outs, err := queryCPPN(coord, cppn); err != nil { + if link, err := fastNetworkLink(coordinates, cppn, useLeo, in, oi, options); err != nil { return nil, err - } else if use_leo && outs[1] > 0 { - // add links only when CPPN's LEO output signals to - link = createLink(outs[0], in, oi, context.WeightRange) - } else if !use_leo && math.Abs(outs[0]) > context.LinkThreshold { - // add only connections with signal exceeding provided threshold - link = createThresholdNormalizedLink(outs[0], in, oi, context.LinkThreshold, context.WeightRange) - - } - if link != nil { - connections = append(connections, link) + } else if link != nil { + links = append(links, link) // add node and edge to graph - if _, err := addEdgeToBuilder(graph_builder, in, oi, link.Weight); err != nil { + if _, err := addEdgeToBuilder(graphBuilder, in, oi, link.Weight); err != nil { return nil, err } } @@ -279,14 +251,33 @@ func (s *Substrate) CreateNetworkSolver(cppn network.NetworkSolver, use_leo bool } // build activations - activations := make([]utils.NodeActivationType, totalNeuronCount) + activations := make([]neatmath.NodeActivationType, totalNeuronCount) for i := 0; i < totalNeuronCount; i++ { activations[i] = activationForNeuron(i) } + if totalNeuronCount == 0 || len(links) == 0 || len(activations) != totalNeuronCount { + message := fmt.Sprintf("failed to create network solver: links [%d], nodes [%d], activations [%d]", + len(links), totalNeuronCount, len(activations)) + return nil, errors.New(message) + } + // create fast network solver solver := network.NewFastModularNetworkSolver( s.Layout.BiasCount(), s.Layout.InputCount(), s.Layout.OutputCount(), totalNeuronCount, - activations, connections, biasList, nil) + activations, links, biasList, nil) return solver, nil } + +func fastNetworkLink(coordinates []float64, cppn network.Solver, useLeo bool, source, target int, options *hyperneat.Options) (*network.FastNetworkLink, error) { + if outs, err := queryCPPN(coordinates, cppn); err != nil { + return nil, err + } else if useLeo && outs[1] > 0 { + // add links only when CPPN LEO output signals to + return createLink(outs[0], source, target, options.WeightRange), nil + } else if !useLeo && math.Abs(outs[0]) >= options.LinkThreshold { + // add only links with signal exceeding provided threshold + return createThresholdNormalizedLink(outs[0], source, target, options.LinkThreshold, options.WeightRange), nil + } + return nil, nil +} diff --git a/cppn/substrate_graph_builder.go b/cppn/substrate_graph_builder.go index e969922..c2a8a81 100644 --- a/cppn/substrate_graph_builder.go +++ b/cppn/substrate_graph_builder.go @@ -2,11 +2,10 @@ package cppn import ( "errors" - "io" - "github.com/yaricom/goGraphML/graphml" - "github.com/yaricom/goNEAT/neat/network" - "github.com/yaricom/goNEAT/neat/utils" + "github.com/yaricom/goNEAT/v4/neat/math" + "github.com/yaricom/goNEAT/v4/neat/network" + "io" ) const ( @@ -20,21 +19,21 @@ const ( edgeAttrTargetId = "targetId" ) -// The graph builder able to build weighted directed graphs representing substrate networks +// SubstrateGraphBuilder The graph builder able to build weighted directed graphs representing substrate networks type SubstrateGraphBuilder interface { - // Adds specified node to the graph with provided position - AddNode(nodeId int, nodeNeuronType network.NodeNeuronType, nodeActivation utils.NodeActivationType, position *PointF) error - // Adds edge between two graph nodes + // AddNode Adds specified node to the graph with provided position + AddNode(nodeId int, nodeNeuronType network.NodeNeuronType, nodeActivation math.NodeActivationType, position *PointF) error + // AddWeightedEdge Adds edge between two graph nodes AddWeightedEdge(sourceId, targetId int, weight float64) error - // Returns the number of nodes in the graph + // NodesCount Returns the number of nodes in the graph NodesCount() (int, error) - // Returns the number of edges in the graph + // EdgesCount Returns the number of edges in the graph EdgesCount() (int, error) - // Marshal graph to the provided writer + // Marshal the graph to the provided writer Marshal(w io.Writer) error - // Unmarshal graph from the provided reader + // UnMarshal is to unmarshal graph from the provided reader UnMarshal(r io.Reader) error } @@ -50,8 +49,8 @@ type graphMLBuilder struct { nodesMap map[int]*graphml.Node } -// Creates new instance with specified description to be included into serialized graph. If compact is true than graph -// will be marshaled into compact form +// NewSubstrateGraphMLBuilder Creates new instance with specified description to be included into serialized graph. +// If compact is true then graph will be marshaled into compact form func NewSubstrateGraphMLBuilder(description string, compact bool) SubstrateGraphBuilder { return &graphMLBuilder{ nodesMap: make(map[int]*graphml.Node), @@ -60,12 +59,13 @@ func NewSubstrateGraphMLBuilder(description string, compact bool) SubstrateGraph } } -func (b *graphMLBuilder) AddNode(nodeId int, nodeNeuronType network.NodeNeuronType, nodeActivation utils.NodeActivationType, position *PointF) (err error) { +func (b *graphMLBuilder) AddNode(nodeId int, nodeNeuronType network.NodeNeuronType, + nodeActivation math.NodeActivationType, position *PointF) (err error) { // create attributes map nodeAttr := make(map[string]interface{}) nodeAttr[nodeAttrID] = nodeId nodeAttr[nodeAttrNodeNeuronType] = network.NeuronTypeName(nodeNeuronType) - if nodeAttr[nodeAttrNodeActivationType], err = utils.NodeActivators.ActivationNameFromType(nodeActivation); err != nil { + if nodeAttr[nodeAttrNodeActivationType], err = math.NodeActivators.ActivationNameFromType(nodeActivation); err != nil { return err } nodeAttr[nodeAttrX] = position.X @@ -83,7 +83,7 @@ func (b *graphMLBuilder) AddNode(nodeId int, nodeNeuronType network.NodeNeuronTy return nil } -func (b *graphMLBuilder) AddWeightedEdge(sourceId, targetId int, weight float64) (err error) { +func (b *graphMLBuilder) AddWeightedEdge(sourceId, targetId int, weight float64) error { // create attributes map edgeAttr := make(map[string]interface{}) edgeAttr[edgeAttrWeight] = weight @@ -102,10 +102,10 @@ func (b *graphMLBuilder) AddWeightedEdge(sourceId, targetId int, weight float64) if graph, err := b.graph(); err != nil { return err - } else { - _, err = graph.AddEdge(source, target, edgeAttr, graphml.EdgeDirectionDefault, "") + } else if _, err = graph.AddEdge(source, target, edgeAttr, graphml.EdgeDirectionDefault, ""); err != nil { + return err } - return err + return nil } func (b *graphMLBuilder) NodesCount() (int, error) { @@ -150,7 +150,8 @@ func (b *graphMLBuilder) graph() (*graphml.Graph, error) { return b.graphML.Graphs[0], nil } -func addNodeToBuilder(builder SubstrateGraphBuilder, nodeId int, nodeType network.NodeNeuronType, nodeActivation utils.NodeActivationType, position *PointF) (bool, error) { +func addNodeToBuilder(builder SubstrateGraphBuilder, nodeId int, nodeType network.NodeNeuronType, + nodeActivation math.NodeActivationType, position *PointF) (bool, error) { if builder == nil { return false, nil } else if err := builder.AddNode(nodeId, nodeType, nodeActivation, position); err != nil { diff --git a/cppn/substrate_graph_builder_test.go b/cppn/substrate_graph_builder_test.go index 29a7ed2..faf03b7 100644 --- a/cppn/substrate_graph_builder_test.go +++ b/cppn/substrate_graph_builder_test.go @@ -4,11 +4,10 @@ import ( "bytes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/yaricom/goNEAT/v4/neat/math" + "github.com/yaricom/goNEAT/v4/neat/network" "strings" "testing" - - "github.com/yaricom/goNEAT/neat/network" - "github.com/yaricom/goNEAT/neat/utils" ) func TestNewGraphMLBuilder(t *testing.T) { @@ -33,7 +32,7 @@ func TestGraphMLBuilder_AddNode(t *testing.T) { err := builder.AddNode( node[nodeAttrID].(int), node[nodeAttrNodeNeuronType].(network.NodeNeuronType), - node[nodeAttrNodeActivationType].(utils.NodeActivationType), + node[nodeAttrNodeActivationType].(math.NodeActivationType), position) require.NoError(t, err, "failed to add node") } @@ -53,7 +52,7 @@ func TestGraphMLBuilder_AddNode(t *testing.T) { nnt := network.NeuronTypeName(v.(network.NodeNeuronType)) assert.Equal(t, nnt, gnAttr[k], "wrong neuron type") } else if k == nodeAttrNodeActivationType { - nat, err := utils.NodeActivators.ActivationNameFromType(v.(utils.NodeActivationType)) + nat, err := math.NodeActivators.ActivationNameFromType(v.(math.NodeActivationType)) assert.NoError(t, err, "failed to get activation name from type: %v", v) assert.Equal(t, nat, gnAttr[k], "wrong activation type") } else if gnAttr[k] != v { @@ -74,7 +73,7 @@ func TestGraphMLBuilder_AddWeightedEdge(t *testing.T) { err := builder.AddNode( node[nodeAttrID].(int), node[nodeAttrNodeNeuronType].(network.NodeNeuronType), - node[nodeAttrNodeActivationType].(utils.NodeActivationType), + node[nodeAttrNodeActivationType].(math.NodeActivationType), position) require.NoError(t, err, "failed to add node: %v", node) } @@ -111,7 +110,7 @@ func TestGraphMLBuilder_Marshal(t *testing.T) { err := builder.AddNode( node[nodeAttrID].(int), node[nodeAttrNodeNeuronType].(network.NodeNeuronType), - node[nodeAttrNodeActivationType].(utils.NodeActivationType), + node[nodeAttrNodeActivationType].(math.NodeActivationType), position) require.NoError(t, err, "failed to add node: %v", node) } @@ -156,11 +155,11 @@ func createTestEdges() []map[string]interface{} { func createTestNodes() []map[string]interface{} { return []map[string]interface{}{ - {"id": 1, "X": -0.5, "Y": -1.0, "NodeNeuronType": network.InputNeuron, "NodeActivationType": utils.NullActivation}, - {"id": 2, "X": 0.5, "Y": -1.0, "NodeNeuronType": network.InputNeuron, "NodeActivationType": utils.NullActivation}, - {"id": 3, "X": 0.0, "Y": 0.0, "NodeNeuronType": network.HiddenNeuron, "NodeActivationType": utils.SigmoidSteepenedActivation}, - {"id": 4, "X": 0.0, "Y": 0.0, "NodeNeuronType": network.HiddenNeuron, "NodeActivationType": utils.SigmoidSteepenedActivation}, - {"id": 5, "X": 0.0, "Y": 1.0, "NodeNeuronType": network.OutputNeuron, "NodeActivationType": utils.LinearActivation}, + {"id": 1, "X": -0.5, "Y": -1.0, "NodeNeuronType": network.InputNeuron, "NodeActivationType": math.NullActivation}, + {"id": 2, "X": 0.5, "Y": -1.0, "NodeNeuronType": network.InputNeuron, "NodeActivationType": math.NullActivation}, + {"id": 3, "X": 0.0, "Y": 0.0, "NodeNeuronType": network.HiddenNeuron, "NodeActivationType": math.SigmoidSteepenedActivation}, + {"id": 4, "X": 0.0, "Y": 0.0, "NodeNeuronType": network.HiddenNeuron, "NodeActivationType": math.SigmoidSteepenedActivation}, + {"id": 5, "X": 0.0, "Y": 1.0, "NodeNeuronType": network.OutputNeuron, "NodeActivationType": math.LinearActivation}, } } diff --git a/cppn/substrate_layout.go b/cppn/substrate_layout.go index 5925bc5..96011b9 100644 --- a/cppn/substrate_layout.go +++ b/cppn/substrate_layout.go @@ -3,25 +3,25 @@ package cppn import ( "errors" "fmt" - "github.com/yaricom/goNEAT/neat/network" + "github.com/yaricom/goNEAT/v4/neat/network" ) -// Defines layout of neurons in the substrate +// SubstrateLayout Defines layout of neurons in the substrate type SubstrateLayout interface { - // Returns coordinates of the neuron with specified index [0; count) and type + // NodePosition Returns coordinates of the neuron with specified index [0; count) and type NodePosition(index int, nType network.NodeNeuronType) (*PointF, error) - // Returns number of BIAS neurons in the layout + // BiasCount Returns number of BIAS neurons in the layout BiasCount() int - // Returns number of INPUT neurons in the layout + // InputCount Returns number of INPUT neurons in the layout InputCount() int - // Returns number of HIDDEN neurons in the layout + // HiddenCount Returns number of HIDDEN neurons in the layout HiddenCount() int - // Returns number of OUTPUT neurons in the layout + // OutputCount Returns number of OUTPUT neurons in the layout OutputCount() int } -// Defines grid substrate layout +// GridSubstrateLayout Defines grid substrate layout type GridSubstrateLayout struct { // The number of bias nodes encoded in this substrate biasCount int @@ -40,7 +40,7 @@ type GridSubstrateLayout struct { outputDelta float64 } -// Creates new instance with specified number of nodes to create layout for +// NewGridSubstrateLayout Creates new instance with specified number of nodes to create layout for func NewGridSubstrateLayout(biasCount, inputCount, outputCount, hiddenCount int) *GridSubstrateLayout { s := GridSubstrateLayout{biasCount: biasCount, inputCount: inputCount, outputCount: outputCount, hiddenCount: hiddenCount} @@ -90,8 +90,6 @@ func (g *GridSubstrateLayout) NodePosition(index int, nType network.NodeNeuronTy if index >= count { return nil, errors.New("neuron index is out of range") - } else if nType == network.BiasNeuron { - return &point, nil } // calculate X position point.X = -1.0 + delta/2.0 // the initial position with half delta shift diff --git a/cppn/substrate_layout_test.go b/cppn/substrate_layout_test.go index ec5b904..1cca679 100644 --- a/cppn/substrate_layout_test.go +++ b/cppn/substrate_layout_test.go @@ -2,7 +2,7 @@ package cppn import ( "github.com/stretchr/testify/require" - "github.com/yaricom/goNEAT/neat/network" + "github.com/yaricom/goNEAT/v4/neat/network" "testing" ) diff --git a/cppn/substrate_test.go b/cppn/substrate_test.go index fb3de3f..165df2c 100644 --- a/cppn/substrate_test.go +++ b/cppn/substrate_test.go @@ -4,9 +4,8 @@ import ( "bytes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/yaricom/goESHyperNEAT/hyperneat" - "github.com/yaricom/goNEAT/neat/utils" - "os" + "github.com/yaricom/goESHyperNEAT/v2/hyperneat" + "github.com/yaricom/goNEAT/v4/neat/math" "testing" ) @@ -17,8 +16,8 @@ func TestNewSubstrate(t *testing.T) { layout := NewGridSubstrateLayout(biasCount, inputCount, outputCount, hiddenCount) // create new substrate - substr := NewSubstrate(layout, utils.SigmoidSteepenedActivation) - assert.Equal(t, utils.SigmoidSteepenedActivation, substr.NodesActivation) + substr := NewSubstrate(layout, math.SigmoidSteepenedActivation, math.LinearActivation) + assert.Equal(t, math.SigmoidSteepenedActivation, substr.HiddenNodesActivation) } func TestSubstrate_CreateNetworkSolver(t *testing.T) { @@ -26,11 +25,11 @@ func TestSubstrate_CreateNetworkSolver(t *testing.T) { layout := NewGridSubstrateLayout(biasCount, inputCount, outputCount, hiddenCount) // create new substrate - substr := NewSubstrate(layout, utils.SigmoidSteepenedActivation) - assert.Equal(t, utils.SigmoidSteepenedActivation, substr.NodesActivation) + substr := NewSubstrate(layout, math.SigmoidSteepenedActivation, math.LinearActivation) + assert.Equal(t, math.SigmoidSteepenedActivation, substr.HiddenNodesActivation) // create solver from substrate - cppn, err := ReadCPPFromGenomeFile(cppnHyperNEATTestGenomePath) + cppn, err := FastSolverFromGenomeFile(cppnHyperNEATTestGenomePath) require.NoError(t, err, "failed to read CPPN") context, err := loadHyperNeatContext(hyperNeatTestConfigFile) @@ -45,23 +44,12 @@ func TestSubstrate_CreateNetworkSolver(t *testing.T) { totalNodeCount := biasCount + inputCount + hiddenCount + outputCount assert.Equal(t, totalNodeCount, solver.NodeCount(), "wrong nodes number") - totalLinkCount := 12 //biasCount * (hiddenCount + outputCount) + totalLinkCount := 12 assert.Equal(t, totalLinkCount, solver.LinkCount(), "wrong links number") // test outputs - signals := []float64{0.9, 5.2, 1.2, 0.6} - err = solver.LoadSensors(signals) - require.NoError(t, err, "failed to load sensors") - - res, err := solver.RecursiveSteps() - require.NoError(t, err, "failed to perform recursive activation") - require.True(t, res, "failed to relax network") - - outs := solver.ReadOutputs() - outExpected := []float64{0.6427874813512032, 0.8685335941574246} - for i, out := range outs { - assert.Equal(t, outExpected[i], out, "wrong output at: %d", i) - } + outExpected := []float64{1.0768005629123314, 1.131042391465084} + checkNetworkSolverOutputs(solver, outExpected, 0.0, t) } func TestSubstrate_CreateLEONetworkSolver(t *testing.T) { @@ -69,11 +57,11 @@ func TestSubstrate_CreateLEONetworkSolver(t *testing.T) { layout := NewGridSubstrateLayout(biasCount, inputCount, outputCount, hiddenCount) // create new substrate - substr := NewSubstrate(layout, utils.SigmoidSteepenedActivation) - assert.Equal(t, utils.SigmoidSteepenedActivation, substr.NodesActivation) + substr := NewSubstrate(layout, math.SigmoidSteepenedActivation, math.LinearActivation) + assert.Equal(t, math.SigmoidSteepenedActivation, substr.HiddenNodesActivation) // create solver from substrate - cppn, err := ReadCPPFromGenomeFile(cppnLeoHyperNEATTestGenomePath) + cppn, err := FastSolverFromGenomeFile(cppnLeoHyperNEATTestGenomePath) require.NoError(t, err, "failed to read CPPN") context, err := loadHyperNeatContext(hyperNeatTestConfigFile) @@ -84,27 +72,18 @@ func TestSubstrate_CreateLEONetworkSolver(t *testing.T) { solver, err := substr.CreateNetworkSolver(cppn, true, graph, context) require.NoError(t, err, "failed to create network solver") + printGraph(graph, t) + // test solver totalNodeCount := biasCount + inputCount + hiddenCount + outputCount assert.Equal(t, totalNodeCount, solver.NodeCount(), "wrong nodes number") - totalLinkCount := 14 + totalLinkCount := 16 assert.Equal(t, totalLinkCount, solver.LinkCount(), "wrong links number") // test outputs - signals := []float64{0.9, 5.2, 1.2, 0.6} - err = solver.LoadSensors(signals) - require.NoError(t, err, "failed to load sensors") - - res, err := solver.RecursiveSteps() - require.NoError(t, err, "failed to perform recursive activation") - require.True(t, res, "failed to relax network") - - outs := solver.ReadOutputs() - outExpected := []float64{0.5000001657646664, 0.5000003552761682} - for i, out := range outs { - assert.Equal(t, outExpected[i], out, "wrong output at: %d", i) - } + outExpected := []float64{-0.1147701149321737, -0.7584174401602518} + checkNetworkSolverOutputs(solver, outExpected, 0.0, t) } func TestSubstrate_CreateNetworkSolverWithGraphBuilder(t *testing.T) { @@ -115,10 +94,10 @@ func TestSubstrate_CreateNetworkSolverWithGraphBuilder(t *testing.T) { builder := NewSubstrateGraphMLBuilder("", false).(*graphMLBuilder) // create new substrate - substr := NewSubstrate(layout, utils.SigmoidSteepenedActivation) + substr := NewSubstrate(layout, math.SigmoidSteepenedActivation, math.LinearActivation) // create solver from substrate - cppn, err := ReadCPPFromGenomeFile(cppnHyperNEATTestGenomePath) + cppn, err := FastSolverFromGenomeFile(cppnHyperNEATTestGenomePath) require.NoError(t, err, "failed to read CPPN") context, err := loadHyperNeatContext(hyperNeatTestConfigFile) @@ -142,33 +121,18 @@ func TestSubstrate_CreateNetworkSolverWithGraphBuilder(t *testing.T) { require.NoError(t, err, "failed to marshal graph") strOut := buf.String() - assert.Equal(t, 5597, len(strOut), "wrong length of marshalled string") + assert.Equal(t, 5587, len(strOut), "wrong length of marshalled string") // test outputs - signals := []float64{0.9, 5.2, 1.2, 0.6} - err = solver.LoadSensors(signals) - require.NoError(t, err, "failed to load sensors") - - res, err := solver.RecursiveSteps() - require.NoError(t, err, "failed to perform recursive activation") - require.True(t, res, "failed to relax network") - - outs := solver.ReadOutputs() - outExpected := []float64{0.6427874813512032, 0.8685335941574246} - for i, out := range outs { - assert.Equal(t, outExpected[i], out, "wrong output at: %d", i) - } + outExpected := []float64{1.0768005629123314, 1.131042391465084} + checkNetworkSolverOutputs(solver, outExpected, 0.0, t) } // Loads HyperNeat context from provided config file's path -func loadHyperNeatContext(configPath string) (*hyperneat.HyperNEATContext, error) { - if r, err := os.Open(configPath); err != nil { +func loadHyperNeatContext(configPath string) (*hyperneat.Options, error) { + if context, err := hyperneat.LoadYAMLConfigFile(configPath); err != nil { return nil, err } else { - if context, err := hyperneat.Load(r); err != nil { - return nil, err - } else { - return context, nil - } + return context, nil } } diff --git a/data/retina/cppn_genome.yml b/data/retina/cppn_genome.yml index 13259cf..e88fd1e 100644 --- a/data/retina/cppn_genome.yml +++ b/data/retina/cppn_genome.yml @@ -3,33 +3,40 @@ genome: id: 1 # The traits used in this genome traits: - - {id: 1, params: [0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]} - - {id: 2, params: [0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]} - - {id: 3, params: [0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]} - - {id: 4, params: [0.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]} - - {id: 5, params: [0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]} - - {id: 6, params: [0.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]} - - {id: 7, params: [0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]} + - { id: 1, params: [ 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] } + - { id: 2, params: [ 0.2, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] } + - { id: 3, params: [ 0.3, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] } + - { id: 4, params: [ 0.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] } + - { id: 5, params: [ 0.5, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] } + - { id: 6, params: [ 0.6, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] } + - { id: 7, params: [ 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] } + - { id: 8, params: [ 0.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ] } # The neuron nodes for this genome nodes: - - {id: 1, trait_id: 0, type: BIAS, activation: LinearActivation} + - { id: 1, trait_id: 0, type: BIAS, activation: LinearActivation } # The input nodes - sensors - - {id: 2, trait_id: 0, type: INPT, activation: LinearActivation} - - {id: 3, trait_id: 0, type: INPT, activation: LinearActivation} - - {id: 4, trait_id: 0, type: INPT, activation: LinearActivation} - - {id: 5, trait_id: 0, type: INPT, activation: LinearActivation} - # The output nodes - actuators - - {id: 6, trait_id: 0, type: OUTP, activation: TanhActivation} + - { id: 2, trait_id: 0, type: INPT, activation: LinearActivation } # x1 + - { id: 3, trait_id: 0, type: INPT, activation: LinearActivation } # y1 + - { id: 4, trait_id: 0, type: INPT, activation: LinearActivation } # z1 + - { id: 5, trait_id: 0, type: INPT, activation: LinearActivation } # x2 + - { id: 6, trait_id: 0, type: INPT, activation: LinearActivation } # y2 + - { id: 7, trait_id: 0, type: INPT, activation: LinearActivation } # z2 + # The output nodes - actuators SigmoidBipolarActivation to make sure we have output values in range (-1, 1) + - { id: 8, trait_id: 0, type: OUTP, activation: SigmoidBipolarActivation } # The hidden node - - {id: 7, trait_id: 0, type: HIDN, activation: GaussianBipolarActivation} + - { id: 9, trait_id: 0, type: HIDN, activation: GaussianActivation } # G1 + - { id: 10, trait_id: 0, type: HIDN, activation: GaussianActivation } # G2 + # LEO + - { id: 11, trait_id: 0, type: OUTP, activation: StepActivation } # The genes - connection between neuron nodes within this genome genes: - - {src_id: 1, tgt_id: 6, weight: 0.1, trait_id: 1, innov_num: 1, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 2, tgt_id: 6, weight: 0.3, trait_id: 2, innov_num: 2, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 3, tgt_id: 7, weight: 0.2, trait_id: 3, innov_num: 3, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 4, tgt_id: 7, weight: 0.3, trait_id: 4, innov_num: 4, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 5, tgt_id: 6, weight: 0.5, trait_id: 5, innov_num: 5, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 1, tgt_id: 7, weight: 0.4, trait_id: 6, innov_num: 6, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 7, tgt_id: 6, weight: 0.8, trait_id: 7, innov_num: 7, mut_num: 0, recurrent: false, enabled: true} \ No newline at end of file + - { src_id: 1, tgt_id: 10, weight: 0.5, trait_id: 1, innov_num: 1, mut_num: 0, recurrent: false, enabled: true } + - { src_id: 2, tgt_id: 9, weight: 0.5, trait_id: 2, innov_num: 3, mut_num: 0, recurrent: false, enabled: true } + - { src_id: 3, tgt_id: 10, weight: 0.5, trait_id: 3, innov_num: 4, mut_num: 0, recurrent: false, enabled: true } + - { src_id: 5, tgt_id: 9, weight: 0.5, trait_id: 5, innov_num: 6, mut_num: 0, recurrent: false, enabled: true } + - { src_id: 6, tgt_id: 10, weight: 0.5, trait_id: 7, innov_num: 7, mut_num: 0, recurrent: false, enabled: true } + - { src_id: 9, tgt_id: 11, weight: 0.5, trait_id: 8, innov_num: 9, mut_num: 0, recurrent: false, enabled: true } + - { src_id: 10, tgt_id: 8, weight: 0.5, trait_id: 8, innov_num: 10, mut_num: 0, recurrent: false, enabled: true } + - { src_id: 1, tgt_id: 11, weight: 0.5, trait_id: 8, innov_num: 11, mut_num: 0, recurrent: false, enabled: true } \ No newline at end of file diff --git a/data/retina/es_hyper.neat.yml b/data/retina/es_hyper.neat.yml index f097eed..4cc666a 100644 --- a/data/retina/es_hyper.neat.yml +++ b/data/retina/es_hyper.neat.yml @@ -1,131 +1,141 @@ ########################################## # The common NEAT specific configuration # ########################################## -neat: - # Probability of mutating a single trait param - trait_param_mut_prob: 0.5 - # Power of mutation on a single trait param - trait_mutation_power: 1.0 - # The power of a link weight mutation - weight_mut_power: 2.5 - - # 3 global coefficients are used to determine the formula for computing the compatibility between 2 genomes. - # The formula is: disjoint_coeff * pdg + excess_coeff * peg + mutdiff_coeff * mdmg. - # See the Compatibility method in the Genome class for more info - # They can be thought of as the importance of disjoint Genes, excess Genes, and parametric difference between Genes of - # the same function, respectively. - disjoint_coeff: 1.0 - excess_coeff: 1.0 - mutdiff_coeff: 0.4 - - # This global tells compatibility threshold under which two Genomes are considered the same species - compat_threshold: 3.0 - # How much does age matter? Gives a fitness boost up to some young age (niche). If it is 1, then young species get no fitness boost. - age_significance: 1.0 - # Percent of average fitness for survival, how many get to reproduce based on survival_thresh * pop_size - survival_thresh: 0.2 - - # Probabilities of a non-mating reproduction - mutate_only_prob: 0.25 - # Probability of genome trait mutation - mutate_random_trait_prob: 0.1 - # Probability of link trait mutation - mutate_link_trait_prob: 0.1 - # Probability of node trait mutation - mutate_node_trait_prob: 0.1 - # Probability of link weight value mutation - mutate_link_weights_prob: 0.9 - # Probability of enabling/disabling of specific link/gene - mutate_toggle_enable_prob: 0.0 - # Probability of finding the first disabled gene and re-enabling it - mutate_gene_reenable_prob: 0.0 - # Probability of adding new node - mutate_add_node_prob: 0.03 - # Probability of adding new link between nodes - mutate_add_link_prob: 0.08 - # Probability of making connections from disconnected sensors (input, bias type neurons) - mutate_connect_sensors: 0.5 - - # Probability of mating between different species - interspecies_mate_rate: 0.001 - # Probability of mating this Genome with another Genome g. For every point in each Genome, where each Genome shares - # the innovation number, the Gene is chosen randomly from either parent. If one parent has an innovation absent in - # the other, the baby may inherit the innovation if it is from the more fit parent. - mate_multipoint_prob: 0.3 - # Probability of mating like in multipoint, but instead of selecting one or the other when the innovation numbers match, - # it averages their weights. - mate_multipoint_avg_prob: 0.3 - # Probability of mating similar to a standard single point CROSSOVER operator. Traits are averaged as in the previous two - # mating methods. A Gene is chosen in the smaller Genome for splitting. When the Gene is reached, it is averaged with - # the matching Gene from the larger Genome, if one exists. Then every other Gene is taken from the larger Genome. - mate_singlepoint_prob: 0.3 - - # Probability of mating without mutation - mate_only_prob: 0.2 - - # Probability of forcing selection of ONLY links that are naturally recurrent - recur_only_prob: 0.0 - - # The number of babies to stolen off to the champions - babies_stolen: 0 - # The population size as a number of organisms - pop_size: 300 - # Age when Species starts to be penalized - dropoff_age: 50 - # Number of tries mutate_add_link will attempt to find an open link - newlink_tries: 50 - # Tells to print population to file every n generations - print_every: 10 - - # The number of runs to average over in an experiment - num_runs: 100 - # The number of epochs (generations) to execute training - num_generations: 100 - - # The epoch's executor type to apply [sequential, parallel] - epoch_executor: sequential - - # The genome compatibility method to use [linear, fast]. The later is best for bigger genomes - genome_compat_method: fast - - # The log level - log_level: Info - - # The nodes activation functions list to choose from (activation function -> it's selection probability) - node_activators: - - SigmoidBipolarActivation 0.25 - - GaussianBipolarActivation 0.35 - - LinearAbsActivation 0.15 - - SineActivation 0.25 +# Probability of mutating a single trait param +trait_param_mut_prob: 0.5 +# Power of mutation on a single trait param +trait_mutation_power: 1.0 +# The power of a link weight mutation +weight_mut_power: 2.5 + +# 3 global coefficients are used to determine the formula for computing the compatibility between 2 genomes. +# The formula is: disjoint_coeff * pdg + excess_coeff * peg + mutdiff_coeff * mdmg. +# See the Compatibility method in the Genome class for more info +# They can be thought of as the importance of disjoint Genes, excess Genes, and parametric difference between Genes of +# the same function, respectively. +disjoint_coeff: 1.0 +excess_coeff: 1.0 +mutdiff_coeff: 0.4 + +# This global tells compatibility threshold under which two Genomes are considered the same species +compat_threshold: 6.0 +# How much does age matter? Gives a fitness boost up to some young age (niche). If it is 1, then young species get no fitness boost. +age_significance: 1.2 +# Percent of average fitness for survival, how many get to reproduce based on survival_thresh * pop_size +survival_thresh: 0.3 + +# Probabilities of a non-mating reproduction +mutate_only_prob: 0.25 +# Probability of genome trait mutation +mutate_random_trait_prob: 0.1 +# Probability of link trait mutation +mutate_link_trait_prob: 0.1 +# Probability of node trait mutation +mutate_node_trait_prob: 0.1 +# Probability of link weight value mutation +mutate_link_weights_prob: 0.5 +# Probability of enabling/disabling of specific link/gene +mutate_toggle_enable_prob: 0.02 +# Probability of finding the first disabled gene and re-enabling it +mutate_gene_reenable_prob: 0.0 +# Probability of adding new node +mutate_add_node_prob: 0.1 +# Probability of adding new link between nodes +mutate_add_link_prob: 0.5 +# Probability of making connections from disconnected sensors (input, bias type neurons) +mutate_connect_sensors: 0.5 + +# Probability of mating between different species +interspecies_mate_rate: 0.001 +# Probability of mating this Genome with another Genome g. For every point in each Genome, where each Genome shares +# the innovation number, the Gene is chosen randomly from either parent. If one parent has an innovation absent in +# the other, the baby may inherit the innovation if it is from the more fit parent. +mate_multipoint_prob: 0.3 +# Probability of mating like in multipoint, but instead of selecting one or the other when the innovation numbers match, +# it averages their weights. +mate_multipoint_avg_prob: 0.3 +# Probability of mating similar to a standard single point CROSSOVER operator. Traits are averaged as in the previous two +# mating methods. A Gene is chosen in the smaller Genome for splitting. When the Gene is reached, it is averaged with +# the matching Gene from the larger Genome, if one exists. Then every other Gene is taken from the larger Genome. +mate_singlepoint_prob: 0.3 + +# Probability of mating without mutation +mate_only_prob: 0.25 + +# Probability of forcing selection of ONLY links that are naturally recurrent +recur_only_prob: 0.0 + +# The number of babies to stolen off to the champions +babies_stolen: 5 +# The population size as a number of organisms +pop_size: 300 +# Age when Species starts to be penalized +dropoff_age: 35 +# Number of tries mutate_add_link will attempt to find an open link +newlink_tries: 50 +# Tells to print population to file every n generations +print_every: 10 + +# The number of runs to average over in an experiment +num_runs: 1 +# The number of epochs (generations) to execute training +num_generations: 1500 + +# The epoch's executor type to apply [sequential, parallel] +epoch_executor: sequential + +# The genome compatibility method to use [linear, fast]. The latter is best for bigger genomes +genome_compat_method: fast + +# The log level +log_level: info + +# The nodes activation functions list to choose from (activation function -> it's selection probability) +node_activators: + - SigmoidSteepenedActivation 1.0 + - SineActivation 1.0 + - LinearAbsActivation 1.0 + - GaussianBipolarActivation 1.0 ######################################## # The HyperNEAT specific configuration # ######################################## -hyperneat: - # The threshold value to indicate which links should be included - link_threshold: 0.2 +# The threshold value to indicate which links should be included +link_threshold: 0.0 - # The weight range defines the minimum and maximum values for weights on substrate connections - weight_range: 3 +# Indicates whether Link Expression Output (LEO) enabled +leo_enabled: true - # The substrate activation function, determines which activation function each node in the substrate will have. - substrate_activator: SigmoidBipolarActivation +# The weight range defines the minimum and maximum values for weights on substrate connections +weight_range: 1 + +# The activation function for hidden substrate nodes. +substrate_activator: SigmoidBipolarActivation +# The activation function for output substrate nodes. +output_activator: SigmoidPlainActivation + +# The BIAS value of the CPPN network if appropriate [default: 1.0] +cppn_bias: 0.33 ########################################### # The ES-HyperNEAT specific configuration # ########################################### -es-hyperneat: - # InitialDepth defines the initial ES-HyperNEAT sample resolution. - initial_depth: 2 - # Maximal ES-HyperNEAT sample resolution if the variance is still higher than the given division threshold - maximal_depth: 3 - - # DivisionThreshold defines the substrate division threshold. - division_threshold: 0.5 - # VarianceThreshold defines the variance threshold for the initial sampling. - variance_threshold: 0.03 - # BandingThreshold defines the threshold that determines when points are regarded to be in a band. - banding_threshold: 0.3 - - # ESIterations defines how many times ES-HyperNEAT should iteratively discover new hidden nodes. - es_iterations: 1 +# InitialDepth defines the initial ES-HyperNEAT sample resolution. +initial_depth: 2 +# Maximal ES-HyperNEAT sample resolution if the variance is still higher than the given division threshold +maximal_depth: 3 + +# DivisionThreshold defines the substrate division threshold. +division_threshold: 0.5 +# VarianceThreshold defines the variance threshold for the initial sampling. +variance_threshold: 0.03 +# BandingThreshold defines the threshold that determines when points are regarded to be in a band. +banding_threshold: 0.3 + +# Quadtree Dimensions +# The range of the tree. Typically set to 2.0 +width: 1.0 +height: 1.0 + +# ESIterations defines how many times ES-HyperNEAT should iteratively discover new hidden nodes. +es_iterations: 1 diff --git a/data/test/test_cppn_hyperneat_genome.yml b/data/test/test_cppn_hyperneat_genome.yml index 1d25a4f..def2398 100644 --- a/data/test/test_cppn_hyperneat_genome.yml +++ b/data/test/test_cppn_hyperneat_genome.yml @@ -19,17 +19,20 @@ genome: - {id: 3, trait_id: 0, type: INPT, activation: NullActivation} - {id: 4, trait_id: 0, type: INPT, activation: NullActivation} - {id: 5, trait_id: 0, type: INPT, activation: NullActivation} + - {id: 6, trait_id: 0, type: INPT, activation: NullActivation} + - {id: 7, trait_id: 0, type: INPT, activation: NullActivation} # The output nodes - actuators - - {id: 6, trait_id: 0, type: OUTP, activation: LinearActivation} + - {id: 8, trait_id: 0, type: OUTP, activation: TanhActivation} # The hidden node - - {id: 7, trait_id: 0, type: HIDN, activation: SineActivation} + - {id: 9, trait_id: 0, type: HIDN, activation: SineActivation} # The genes - connection between neuron nodes within this genome genes: - - {src_id: 1, tgt_id: 6, weight: 0.1, trait_id: 1, innov_num: 1, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 2, tgt_id: 6, weight: 0.3, trait_id: 2, innov_num: 2, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 3, tgt_id: 7, weight: 0.2, trait_id: 3, innov_num: 3, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 4, tgt_id: 7, weight: 0.3, trait_id: 4, innov_num: 4, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 5, tgt_id: 6, weight: 0.5, trait_id: 5, innov_num: 5, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 1, tgt_id: 7, weight: 0.4, trait_id: 6, innov_num: 6, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 7, tgt_id: 6, weight: 0.8, trait_id: 7, innov_num: 7, mut_num: 0, recurrent: false, enabled: true} \ No newline at end of file + - {src_id: 1, tgt_id: 9, weight: 0.1, trait_id: 1, innov_num: 1, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 2, tgt_id: 9, weight: 0.3, trait_id: 2, innov_num: 2, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 3, tgt_id: 9, weight: 0.2, trait_id: 3, innov_num: 3, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 4, tgt_id: 9, weight: 0.3, trait_id: 4, innov_num: 4, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 5, tgt_id: 9, weight: 0.5, trait_id: 5, innov_num: 5, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 6, tgt_id: 9, weight: 0.4, trait_id: 6, innov_num: 6, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 7, tgt_id: 9, weight: 0.8, trait_id: 7, innov_num: 7, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 9, tgt_id: 8, weight: 0.8, trait_id: 7, innov_num: 8, mut_num: 0, recurrent: false, enabled: true} \ No newline at end of file diff --git a/data/test/test_cppn_leo_hyperneat_genome.yml b/data/test/test_cppn_leo_hyperneat_genome.yml index bcf208d..ac3f3ea 100644 --- a/data/test/test_cppn_leo_hyperneat_genome.yml +++ b/data/test/test_cppn_leo_hyperneat_genome.yml @@ -15,29 +15,36 @@ genome: nodes: - {id: 1, trait_id: 0, type: BIAS, activation: NullActivation} # The input nodes - sensors - - {id: 2, trait_id: 0, type: INPT, activation: NullActivation} - - {id: 3, trait_id: 0, type: INPT, activation: NullActivation} - - {id: 4, trait_id: 0, type: INPT, activation: NullActivation} - - {id: 5, trait_id: 0, type: INPT, activation: NullActivation} + - {id: 2, trait_id: 0, type: INPT, activation: NullActivation} # x1 + - {id: 3, trait_id: 0, type: INPT, activation: NullActivation} # y1 + - {id: 4, trait_id: 0, type: INPT, activation: NullActivation} # z1 + - {id: 5, trait_id: 0, type: INPT, activation: NullActivation} # x2 + - {id: 6, trait_id: 0, type: INPT, activation: NullActivation} # y2 + - {id: 7, trait_id: 0, type: INPT, activation: NullActivation} # z2 # The weight output - - {id: 6, trait_id: 0, type: OUTP, activation: LinearActivation} - # The LEO output - using Gaussian to force local connectivity - - {id: 7, trait_id: 0, type: OUTP, activation: LinearActivation} + - {id: 8, trait_id: 0, type: OUTP, activation: TanhActivation} + # The LEO output + - {id: 9, trait_id: 0, type: OUTP, activation: StepActivation} # The hidden nodes - - {id: 8, trait_id: 0, type: HIDN, activation: SineActivation} - - {id: 9, trait_id: 0, type: HIDN, activation: GaussianBipolarActivation} + - {id: 10, trait_id: 0, type: HIDN, activation: GaussianBipolarActivation} + - {id: 11, trait_id: 0, type: HIDN, activation: GaussianBipolarActivation} # The genes - connection between neuron nodes within this genome genes: - - {src_id: 1, tgt_id: 6, weight: 0.1, trait_id: 1, innov_num: 1, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 2, tgt_id: 6, weight: 0.3, trait_id: 2, innov_num: 2, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 3, tgt_id: 8, weight: 0.2, trait_id: 3, innov_num: 3, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 4, tgt_id: 8, weight: 0.3, trait_id: 4, innov_num: 4, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 5, tgt_id: 6, weight: 0.5, trait_id: 5, innov_num: 5, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 1, tgt_id: 8, weight: 0.4, trait_id: 6, innov_num: 6, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 8, tgt_id: 6, weight: 0.8, trait_id: 7, innov_num: 7, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 1, tgt_id: 10, weight: 0.1, trait_id: 1, innov_num: 1, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 1, tgt_id: 11, weight: 0.1, trait_id: 1, innov_num: 1, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 2, tgt_id: 10, weight: 0.3, trait_id: 2, innov_num: 2, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 3, tgt_id: 10, weight: 0.2, trait_id: 3, innov_num: 3, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 4, tgt_id: 10, weight: 0.3, trait_id: 4, innov_num: 4, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 5, tgt_id: 11, weight: 0.5, trait_id: 5, innov_num: 5, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 6, tgt_id: 11, weight: 0.4, trait_id: 6, innov_num: 6, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 7, tgt_id: 11, weight: 0.8, trait_id: 7, innov_num: 7, mut_num: 0, recurrent: false, enabled: true} + + - {src_id: 10, tgt_id: 8, weight: 0.3, trait_id: 4, innov_num: 8, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 11, tgt_id: 8, weight: 0.3, trait_id: 4, innov_num: 9, mut_num: 0, recurrent: false, enabled: true} # The LEO part with local connectivity among X axis - - {src_id: 2, tgt_id: 9, weight: 0.3, trait_id: 2, innov_num: 2, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 4, tgt_id: 9, weight: 0.3, trait_id: 4, innov_num: 4, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 1, tgt_id: 9, weight: 0.4, trait_id: 1, innov_num: 1, mut_num: 0, recurrent: false, enabled: true} - - {src_id: 9, tgt_id: 7, weight: 0.8, trait_id: 7, innov_num: 7, mut_num: 0, recurrent: false, enabled: true} \ No newline at end of file + - {src_id: 1, tgt_id: 9, weight: 0.4, trait_id: 1, innov_num: 10, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 2, tgt_id: 9, weight: 0.4, trait_id: 1, innov_num: 11, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 5, tgt_id: 9, weight: 0.4, trait_id: 1, innov_num: 12, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 10, tgt_id: 9, weight: 0.3, trait_id: 2, innov_num: 13, mut_num: 0, recurrent: false, enabled: true} + - {src_id: 11, tgt_id: 9, weight: 0.8, trait_id: 7, innov_num: 14, mut_num: 0, recurrent: false, enabled: true} \ No newline at end of file diff --git a/data/test/test_es_hyper.neat.yml b/data/test/test_es_hyper.neat.yml index d923630..7356713 100644 --- a/data/test/test_es_hyper.neat.yml +++ b/data/test/test_es_hyper.neat.yml @@ -1,132 +1,40 @@ -########################################## -# The common NEAT specific configuration # -########################################## -neat: - # Probability of mutating a single trait param - trait_param_mut_prob: 0.5 - # Power of mutation on a single trait param - trait_mutation_power: 1.0 - # The power of a link weight mutation - weight_mut_power: 2.5 - - # 3 global coefficients are used to determine the formula for computing the compatibility between 2 genomes. - # The formula is: disjoint_coeff * pdg + excess_coeff * peg + mutdiff_coeff * mdmg. - # See the Compatibility method in the Genome class for more info - # They can be thought of as the importance of disjoint Genes, excess Genes, and parametric difference between Genes of - # the same function, respectively. - disjoint_coeff: 1.0 - excess_coeff: 1.0 - mutdiff_coeff: 0.4 - - # This global tells compatibility threshold under which two Genomes are considered the same species - compat_threshold: 3.0 - # How much does age matter? Gives a fitness boost up to some young age (niching). If it is 1, then young species get no fitness boost. - age_significance: 1.0 - # Percent of average fitness for survival, how many get to reproduce based on survival_thresh * pop_size - survival_thresh: 0.2 - - # Probabilities of a non-mating reproduction - mutate_only_prob: 0.25 - # Probability of genome trait mutation - mutate_random_trait_prob: 0.1 - # Probability of link trait mutation - mutate_link_trait_prob: 0.1 - # Probability of node trait mutation - mutate_node_trait_prob: 0.1 - # Probability of link weight value mutation - mutate_link_weights_prob: 0.9 - # Probability of enabling/disabling of specific link/gene - mutate_toggle_enable_prob: 0.0 - # Probability of finding the first disabled gene and re-enabling it - mutate_gene_reenable_prob: 0.0 - # Probability of adding new node - mutate_add_node_prob: 0.03 - # Probability of adding new link between nodes - mutate_add_link_prob: 0.08 - # Probability of making connections from disconnected sensors (input, bias type neurons) - mutate_connect_sensors: 0.5 - - # Probability of mating between different species - interspecies_mate_rate: 0.001 - # Probability of mating this Genome with another Genome g. For every point in each Genome, where each Genome shares - # the innovation number, the Gene is chosen randomly from either parent. If one parent has an innovation absent in - # the other, the baby may inherit the innovation if it is from the more fit parent. - mate_multipoint_prob: 0.3 - # Probability of mating like in multipoint, but instead of selecting one or the other when the innovation numbers match, - # it averages their weights. - mate_multipoint_avg_prob: 0.3 - # Probability of mating similar to a standard single point CROSSOVER operator. Traits are averaged as in the previous two - # mating methods. A Gene is chosen in the smaller Genome for splitting. When the Gene is reached, it is averaged with - # the matching Gene from the larger Genome, if one exists. Then every other Gene is taken from the larger Genome. - mate_singlepoint_prob: 0.3 - - # Probability of mating without mutation - mate_only_prob: 0.2 - - # Probability of forcing selection of ONLY links that are naturally recurrent - recur_only_prob: 0.0 - - # The number of babies to stolen off to the champions - babies_stolen: 0 - # The population size as a number of organisms - pop_size: 200 - # Age when Species starts to be penalized - dropoff_age: 50 - # Number of tries mutate_add_link will attempt to find an open link - newlink_tries: 50 - # Tells to print population to file every n generations - print_every: 10 - - # The number of runs to average over in an experiment - num_runs: 100 - # The number of epochs (generations) to execute training - num_generations: 100 - - # The epoch's executor type to apply [sequential, parallel] - epoch_executor: sequential - - # The genome compatibility method to use [linear, fast]. The later is best for bigger genomes - genome_compat_method: fast - - # The log level - log_level: Info - - # The nodes activation functions list to choose from (activation function -> it's selection probability) - node_activators: - - SigmoidBipolarActivation 0.25 - - GaussianBipolarActivation 0.35 - - LinearAbsActivation 0.15 - - SineActivation 0.25 - ######################################## # The HyperNEAT specific configuration # ######################################## -hyperneat: - # The threshold value to indicate which links should be included - link_threshold: 0.2 +# The threshold value to indicate which links should be included +link_threshold: 0.0 + +# Indicates whether Link Expression Output (LEO) enabled +leo_enabled: false - # The weight range defines the minimum and maximum values for weights on substrate connections - weight_range: 3 +# The weight range defines the minimum and maximum values for weights on substrate connections +weight_range: 3 - # The substrate activation function, determines which activation function each node in the substrate will have. - substrate_activator: SigmoidSteepenedActivation +# The substrate activation function, determines which activation function each node in the substrate will have. +substrate_activator: SigmoidSteepenedActivation +# The activation function for output substrate nodes. +output_activator: SigmoidPlainActivation ########################################### # The ES-HyperNEAT specific configuration # ########################################### -es-hyperneat: - # InitialDepth defines the initial ES-HyperNEAT sample resolution. - initial_depth: 3 - # Maximal ES-HyperNEAT sample resolution if the variance is still higher than the given division threshold - maximal_depth: 5 - - # DivisionThreshold defines the substrate division threshold. - division_threshold: 0.01 - # VarianceThreshold defines the variance threshold for the initial sampling. - variance_threshold: 0.03 - # BandingThreshold defines the threshold that determines when points are regarded to be in a band. - banding_threshold: 0.3 - - # ESIterations defines how many times ES-HyperNEAT should iteratively discover new hidden nodes. - # TODO BUG WHEN ES_ITERATIONS > 1 - es_iterations: 1 +# InitialDepth defines the initial ES-HyperNEAT sample resolution. +initial_depth: 3 +# Maximal ES-HyperNEAT sample resolution if the variance is still higher than the given division threshold +maximal_depth: 5 + +# DivisionThreshold defines the substrate division threshold. +division_threshold: 0.01 +# VarianceThreshold defines the variance threshold for the initial sampling. +variance_threshold: 0.03 +# BandingThreshold defines the threshold that determines when points are regarded to be in a band. +banding_threshold: 0.3 + +# Quadtree Dimensions +# The range of the tree. Typically set to 2.0 +width: 1.0 +height: 1.0 + +# ESIterations defines how many times ES-HyperNEAT should iteratively discover new hidden nodes. +# TODO BUG WHEN ES_ITERATIONS > 1 +es_iterations: 1 diff --git a/data/test/test_hyper.neat.yml b/data/test/test_hyper.neat.yml index e0302e2..91388c9 100644 --- a/data/test/test_hyper.neat.yml +++ b/data/test/test_hyper.neat.yml @@ -1,112 +1,13 @@ -########################################## -# The common NEAT specific configuration # -########################################## -neat: - # Probability of mutating a single trait param - trait_param_mut_prob: 0.5 - # Power of mutation on a single trait param - trait_mutation_power: 1.0 - # The power of a link weight mutation - weight_mut_power: 2.5 - - # 3 global coefficients are used to determine the formula for computing the compatibility between 2 genomes. - # The formula is: disjoint_coeff * pdg + excess_coeff * peg + mutdiff_coeff * mdmg. - # See the Compatibility method in the Genome class for more info - # They can be thought of as the importance of disjoint Genes, excess Genes, and parametric difference between Genes of - # the same function, respectively. - disjoint_coeff: 1.0 - excess_coeff: 1.0 - mutdiff_coeff: 0.4 - - # This global tells compatibility threshold under which two Genomes are considered the same species - compat_threshold: 3.0 - # How much does age matter? Gives a fitness boost up to some young age (niching). If it is 1, then young species get no fitness boost. - age_significance: 1.0 - # Percent of average fitness for survival, how many get to reproduce based on survival_thresh * pop_size - survival_thresh: 0.2 - - # Probabilities of a non-mating reproduction - mutate_only_prob: 0.25 - # Probability of genome trait mutation - mutate_random_trait_prob: 0.1 - # Probability of link trait mutation - mutate_link_trait_prob: 0.1 - # Probability of node trait mutation - mutate_node_trait_prob: 0.1 - # Probability of link weight value mutation - mutate_link_weights_prob: 0.9 - # Probability of enabling/disabling of specific link/gene - mutate_toggle_enable_prob: 0.0 - # Probability of finding the first disabled gene and re-enabling it - mutate_gene_reenable_prob: 0.0 - # Probability of adding new node - mutate_add_node_prob: 0.03 - # Probability of adding new link between nodes - mutate_add_link_prob: 0.08 - # Probability of making connections from disconnected sensors (input, bias type neurons) - mutate_connect_sensors: 0.5 - - # Probability of mating between different species - interspecies_mate_rate: 0.001 - # Probability of mating this Genome with another Genome g. For every point in each Genome, where each Genome shares - # the innovation number, the Gene is chosen randomly from either parent. If one parent has an innovation absent in - # the other, the baby may inherit the innovation if it is from the more fit parent. - mate_multipoint_prob: 0.3 - # Probability of mating like in multipoint, but instead of selecting one or the other when the innovation numbers match, - # it averages their weights. - mate_multipoint_avg_prob: 0.3 - # Probability of mating similar to a standard single point CROSSOVER operator. Traits are averaged as in the previous two - # mating methods. A Gene is chosen in the smaller Genome for splitting. When the Gene is reached, it is averaged with - # the matching Gene from the larger Genome, if one exists. Then every other Gene is taken from the larger Genome. - mate_singlepoint_prob: 0.3 - - # Probability of mating without mutation - mate_only_prob: 0.2 - - # Probability of forcing selection of ONLY links that are naturally recurrent - recur_only_prob: 0.0 - - # The number of babies to stolen off to the champions - babies_stolen: 0 - # The population size as a number of organisms - pop_size: 200 - # Age when Species starts to be penalized - dropoff_age: 50 - # Number of tries mutate_add_link will attempt to find an open link - newlink_tries: 50 - # Tells to print population to file every n generations - print_every: 10 - - # The number of runs to average over in an experiment - num_runs: 100 - # The number of epochs (generations) to execute training - num_generations: 100 - - # The epoch's executor type to apply [sequential, parallel] - epoch_executor: sequential - - # The genome compatibility method to use [linear, fast]. The later is best for bigger genomes - genome_compat_method: fast - - # The log level - log_level: Info - - # The nodes activation functions list to choose from (activation function -> it's selection probability) - node_activators: - - SigmoidBipolarActivation 0.25 - - GaussianBipolarActivation 0.35 - - LinearAbsActivation 0.15 - - SineActivation 0.25 - ######################################## # The HyperNEAT specific configuration # ######################################## -hyperneat: - # The threshold value to indicate which links should be included - link_threshold: 0.2 +# The threshold value to indicate which links should be included +link_threshold: 0.2 - # The weight range defines the minimum and maximum values for weights on substrate connections - weight_range: 3 +# The weight range defines the minimum and maximum values for weights on substrate connections +weight_range: 3 - # The substrate activation function, determines which activation function each node in the substrate will have. - substrate_activator: SigmoidSteepenedActivation \ No newline at end of file +# The substrate activation function, determines which activation function each node in the substrate will have. +substrate_activator: SigmoidSteepenedActivation +# The activation function for output substrate nodes. +output_activator: SigmoidPlainActivation \ No newline at end of file diff --git a/eshyperneat/context.go b/eshyperneat/context.go new file mode 100644 index 0000000..b466278 --- /dev/null +++ b/eshyperneat/context.go @@ -0,0 +1,28 @@ +package eshyperneat + +import ( + "context" + "errors" +) + +var ErrESHyperNEATOptionsNotFound = errors.New("ES-HyperNEAT options not found in the context") + +// key is an unexported type for keys defined in this package. +// This prevents collisions with keys defined in other packages. +type key int + +// esHyperNeatOptionsKey is the key for eshyperneat.Options value in Contexts. It is +// unexported; clients use eshyperneat.NewContext and eshyperneat.FromContext +// instead of using this key directly. +var esHyperNeatOptionsKey key + +// NewContext returns a new Context that carries value of ES-HyperNEAT options. +func NewContext(ctx context.Context, opts *Options) context.Context { + return context.WithValue(ctx, esHyperNeatOptionsKey, opts) +} + +// FromContext returns the ES-HyperNEAT Options value stored in ctx, if any. +func FromContext(ctx context.Context) (*Options, bool) { + u, ok := ctx.Value(esHyperNeatOptionsKey).(*Options) + return u, ok +} diff --git a/eshyperneat/es_hyper_neat.go b/eshyperneat/es_hyper_neat.go index cc9c752..6307f00 100644 --- a/eshyperneat/es_hyper_neat.go +++ b/eshyperneat/es_hyper_neat.go @@ -1,79 +1,65 @@ +// Package eshyperneat holds implementation of Evolvable-Substrate HyperNEAT context package eshyperneat import ( - "bytes" - "errors" - "github.com/spf13/viper" - "github.com/yaricom/goESHyperNEAT/hyperneat" + "github.com/pkg/errors" + "github.com/yaricom/goESHyperNEAT/v2/hyperneat" + "gopkg.in/yaml.v3" "io" + "os" ) -// ES-HyperNEAT execution context -type ESHyperNEATContext struct { - // The included HyperNEAT context - *hyperneat.HyperNEATContext +// Options ES-HyperNEAT execution options +type Options struct { + // The included HyperNEAT options + *hyperneat.Options `yaml:",inline"` // InitialDepth defines the initial ES-HyperNEAT sample resolution. - InitialDepth int + InitialDepth int `yaml:"initial_depth"` // Maximal ES-HyperNEAT sample resolution if the variance is still higher than the given division threshold - MaximalDepth int + MaximalDepth int `yaml:"maximal_depth"` // DivisionThreshold defines the division threshold. If the variance in a region is greater than this value, after // the initial resolution is reached, ES-HyperNEAT will sample down further (values greater than 1.0 will disable // this feature). Note that sampling at really high resolutions can become computationally expensive. - DivisionThreshold float64 + DivisionThreshold float64 `yaml:"division_threshold"` // VarianceThreshold defines the variance threshold for the initial sampling. The bigger this value the less new // connections will be added directly and the more chances that the new collection will be included in bands // (see BandingThreshold) - VarianceThreshold float64 + VarianceThreshold float64 `yaml:"variance_threshold"` // BandingThreshold defines the threshold that determines when points are regarded to be in a band. If the point // is in the band then no new connection will be added and as result no new hidden node will be introduced. - // The bigger this value the less connections/hidden nodes will be added, i.e. wide bands approximation. - BandingThreshold float64 + // The bigger this value the fewer connections/hidden nodes will be added, i.e. wide bands approximation. + BandingThreshold float64 `yaml:"banding_threshold"` + + // Quadtree Dimensions + // The range of the tree. Typically set to 2.0 + Width float64 `yaml:"width"` + Height float64 `yaml:"height"` // ESIterations defines how many times ES-HyperNEAT should iteratively discover new hidden nodes. - ESIterations int + ESIterations int `yaml:"es_iterations"` } -// Loads ESHyperNEAT context options from provided reader -func Load(r io.Reader) (*ESHyperNEATContext, error) { - var buff bytes.Buffer - tee := io.TeeReader(r, &buff) - - // load HyperNEAT options - hCtx, err := hyperneat.Load(tee) +// LoadYAMLOptions is to load ES-HyperNEAT options from provided reader +func LoadYAMLOptions(r io.Reader) (*Options, error) { + content, err := io.ReadAll(r) if err != nil { return nil, err } - - // load ES options - ctx := &ESHyperNEATContext{HyperNEATContext: hCtx} - if err := ctx.load(&buff); err != nil { - return nil, err + // read options + var opts Options + if err = yaml.Unmarshal(content, &opts); err != nil { + return nil, errors.Wrap(err, "failed to decode ES-HyperNEAT options from YAML") } - - return ctx, nil + return &opts, nil } -func (e *ESHyperNEATContext) load(r io.Reader) error { - viper.SetConfigType("YAML") - err := viper.ReadConfig(r) +// LoadYAMLConfigFile is to load ES-HyperNEAT options from provided configuration file +func LoadYAMLConfigFile(path string) (*Options, error) { + configFile, err := os.Open(path) if err != nil { - return err + return nil, errors.Wrap(err, "failed to open ES-HyperNEAT configuration file") } - v := viper.Sub("es-hyperneat") - if v == nil { - return errors.New("es-hyperneat subsection not found in configuration") - } - - e.InitialDepth = v.GetInt("initial_depth") - e.MaximalDepth = v.GetInt("maximal_depth") - - e.DivisionThreshold = v.GetFloat64("division_threshold") - e.VarianceThreshold = v.GetFloat64("variance_threshold") - e.BandingThreshold = v.GetFloat64("banding_threshold") - - e.ESIterations = v.GetInt("es_iterations") - - return nil + return LoadYAMLOptions(configFile) } diff --git a/eshyperneat/es_hyper_neat_test.go b/eshyperneat/es_hyper_neat_test.go index 3b31ef8..ca10fa7 100644 --- a/eshyperneat/es_hyper_neat_test.go +++ b/eshyperneat/es_hyper_neat_test.go @@ -3,25 +3,40 @@ package eshyperneat import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/yaricom/goNEAT/neat/utils" + "github.com/yaricom/goNEAT/v4/neat/math" "os" "testing" ) -func TestESHyperNEATContext_LoadFullContext(t *testing.T) { - r, err := os.Open("../data/test/test_es_hyper.neat.yml") - require.NoError(t, err, "failed to open config file") +const EsHyperNeatCfgPath = "../data/test/test_es_hyper.neat.yml" - esCtx, err := Load(r) - require.NoError(t, err, "failed to load context") +func TestLoadYAMLConfigFile(t *testing.T) { + opts, err := LoadYAMLConfigFile(EsHyperNeatCfgPath) + require.NoError(t, err, "failed to load options from config file") // check loaded values - assert.Equal(t, 3, esCtx.InitialDepth) - assert.Equal(t, 5, esCtx.MaximalDepth) - assert.Equal(t, 0.01, esCtx.DivisionThreshold) - assert.Equal(t, 0.03, esCtx.VarianceThreshold) - assert.Equal(t, 0.3, esCtx.BandingThreshold) - assert.Equal(t, 1, esCtx.ESIterations) - - assert.Equal(t, utils.SigmoidSteepenedActivation, esCtx.HyperNEATContext.SubstrateActivator) + checkEsHyperNeatOptions(opts, t) +} + +func TestLoadYAMLOptions(t *testing.T) { + configFile, err := os.Open(EsHyperNeatCfgPath) + assert.NoError(t, err, "Failed to open ES-HyperNEAT configuration file.") + + opts, err := LoadYAMLOptions(configFile) + assert.NoError(t, err, "failed to load options from config file") + + // check loaded values + checkEsHyperNeatOptions(opts, t) +} + +func checkEsHyperNeatOptions(opts *Options, t *testing.T) { + assert.Equal(t, 3, opts.InitialDepth) + assert.Equal(t, 5, opts.MaximalDepth) + assert.Equal(t, 0.01, opts.DivisionThreshold) + assert.Equal(t, 0.03, opts.VarianceThreshold) + assert.Equal(t, 0.3, opts.BandingThreshold) + assert.Equal(t, 1, opts.ESIterations) + + assert.Equal(t, math.SigmoidSteepenedActivation, opts.SubstrateActivator.SubstrateActivationType) + assert.Equal(t, math.SigmoidPlainActivation, opts.OutputActivator.OutputActivationType) } diff --git a/examples/common.go b/examples/common.go new file mode 100644 index 0000000..e1c7d87 --- /dev/null +++ b/examples/common.go @@ -0,0 +1,24 @@ +package examples + +import "github.com/yaricom/goNEAT/v4/neat" + +const ( + compatibilityThresholdStep = 0.1 + compatibilityThresholdMinValue = 0.3 +) + +// AdjustSpeciesNumber is to adjust species count by keeping it constant +func AdjustSpeciesNumber(speciesCount, epochId, adjustFrequency, numberSpeciesTarget int, options *neat.Options) { + if epochId%adjustFrequency == 0 { + if speciesCount < numberSpeciesTarget { + options.CompatThreshold -= compatibilityThresholdStep + } else if speciesCount > numberSpeciesTarget { + options.CompatThreshold += compatibilityThresholdStep + } + + // to avoid dropping too low + if options.CompatThreshold < compatibilityThresholdMinValue { + options.CompatThreshold = compatibilityThresholdMinValue + } + } +} diff --git a/experiments/retina/dataset.go b/examples/retina/dataset.go similarity index 79% rename from experiments/retina/dataset.go rename to examples/retina/dataset.go index 982c745..87b5d68 100644 --- a/experiments/retina/dataset.go +++ b/examples/retina/dataset.go @@ -6,22 +6,22 @@ func CreateRetinaDataset() []VisualObject { // set left side objects objs = append(objs, NewVisualObject(BothSide, ". .\n. .")) objs = append(objs, NewVisualObject(BothSide, ". .\n. o")) - objs = append(objs, NewVisualObject(LeftSide, ". o\n. o")) - objs = append(objs, NewVisualObject(BothSide, ". o\n. .")) - objs = append(objs, NewVisualObject(LeftSide, ". o\no o")) objs = append(objs, NewVisualObject(BothSide, ". .\no .")) - objs = append(objs, NewVisualObject(LeftSide, "o o\n. o")) + objs = append(objs, NewVisualObject(BothSide, ". o\n. .")) objs = append(objs, NewVisualObject(BothSide, "o .\n. .")) + objs = append(objs, NewVisualObject(LeftSide, ". .\no o")) + objs = append(objs, NewVisualObject(LeftSide, ". o\no o")) + objs = append(objs, NewVisualObject(LeftSide, "o .\no o")) // set right side objects objs = append(objs, NewVisualObject(BothSide, ". .\n. .")) - objs = append(objs, NewVisualObject(BothSide, "o .\n. .")) - objs = append(objs, NewVisualObject(RightSide, "o .\no .")) + objs = append(objs, NewVisualObject(BothSide, ". .\n. o")) objs = append(objs, NewVisualObject(BothSide, ". .\no .")) - objs = append(objs, NewVisualObject(RightSide, "o o\no .")) objs = append(objs, NewVisualObject(BothSide, ". o\n. .")) - objs = append(objs, NewVisualObject(RightSide, "o .\no o")) - objs = append(objs, NewVisualObject(BothSide, ". .\n. o")) + objs = append(objs, NewVisualObject(BothSide, "o .\n. .")) + objs = append(objs, NewVisualObject(RightSide, "o o\n. .")) + objs = append(objs, NewVisualObject(RightSide, "o o\no .")) + objs = append(objs, NewVisualObject(RightSide, "o o\n. o")) return objs } diff --git a/experiments/retina/dataset_test.go b/examples/retina/dataset_test.go similarity index 100% rename from experiments/retina/dataset_test.go rename to examples/retina/dataset_test.go diff --git a/experiments/retina/environment.go b/examples/retina/environment.go similarity index 82% rename from experiments/retina/environment.go rename to examples/retina/environment.go index a3d1198..24bdb0a 100644 --- a/experiments/retina/environment.go +++ b/examples/retina/environment.go @@ -3,7 +3,6 @@ package retina import ( "fmt" "github.com/pkg/errors" - "github.com/yaricom/goESHyperNEAT/eshyperneat" "strings" ) @@ -22,14 +21,15 @@ func (s DetectionSide) String() string { // Environment holds the dataset and evaluation methods for the modular retina experiment type Environment struct { + // the data set of visual objects to be detected visualObjects []VisualObject - inputSize int - context *eshyperneat.ESHyperNEATContext + // the size of input data array + inputSize int } // NewRetinaEnvironment creates a new Retina Environment with a dataset of all possible Visual Object with specified -// number of inputs to be acquired from provided objects -func NewRetinaEnvironment(dataSet []VisualObject, inputSize int, context *eshyperneat.ESHyperNEATContext) (*Environment, error) { +// number of inputs to be acquired from provided objects. +func NewRetinaEnvironment(dataSet []VisualObject, inputSize int) (*Environment, error) { // check that provided visual objects has data points equal to the inputSize for _, o := range dataSet { if len(o.data) != inputSize { @@ -38,7 +38,10 @@ func NewRetinaEnvironment(dataSet []VisualObject, inputSize int, context *eshype inputSize, len(o.data), o) } } - return &Environment{visualObjects: dataSet, inputSize: inputSize, context: context}, nil + return &Environment{ + visualObjects: dataSet, + inputSize: inputSize, + }, nil } // VisualObject represents a left, right, or both, object classified by retina @@ -65,7 +68,7 @@ func (o *VisualObject) String() string { } // parseVisualObjectConfig parses config semantically in the format -// (config = "x1 x2 \n x3 x4") to [ x1, x2, x3, x4 ] where if xi == "o" => xi = 1 +// (config = "x1 x2 \n x3 x4") to [ xf1, xf2, xf3, xf4 ] where if xi == "o" => xfi = 1.0 func parseVisualObjectConfig(config string) []float64 { data := make([]float64, 0) lines := strings.Split(config, "\n") diff --git a/experiments/retina/environment_test.go b/examples/retina/environment_test.go similarity index 100% rename from experiments/retina/environment_test.go rename to examples/retina/environment_test.go diff --git a/examples/retina/retina.go b/examples/retina/retina.go new file mode 100644 index 0000000..08bab42 --- /dev/null +++ b/examples/retina/retina.go @@ -0,0 +1,332 @@ +// Package retina provides implementation of the retina experiment +package retina + +import ( + "context" + "fmt" + "github.com/pkg/errors" + "github.com/yaricom/goESHyperNEAT/v2/cppn" + "github.com/yaricom/goESHyperNEAT/v2/eshyperneat" + "github.com/yaricom/goESHyperNEAT/v2/examples" + "github.com/yaricom/goNEAT/v4/experiment" + "github.com/yaricom/goNEAT/v4/experiment/utils" + "github.com/yaricom/goNEAT/v4/neat" + "github.com/yaricom/goNEAT/v4/neat/genetics" + "github.com/yaricom/goNEAT/v4/neat/network" + "math" + "os" + "time" +) + +const ( + // maxFitness Used as max value which we add error too to get an organism's fitness + maxFitness = 1000.0 + // fitnessThreshold is the fitness value for which an organism is considered to have won the experiment + fitnessThreshold = maxFitness + + debug = true +) + +type generationEvaluator struct { + outDir string + env *Environment + + // The target number of species to be maintained + numSpeciesTarget int + // The species compatibility threshold adjustment frequency + compatAdjustFreq int +} + +// NewGenerationEvaluator is to create new generation's evaluator for retina experiment. The numSpeciesTarget specifies the +// target number of species to maintain in the population. If the number of species differ from the numSpeciesTarget it +// will be automatically adjusted with compatAdjustFreq frequency, i.e., at each epoch % compatAdjustFreq == 0 +func NewGenerationEvaluator(outDir string, env *Environment, numSpeciesTarget, compatAdjustFreq int) (experiment.GenerationEvaluator, experiment.TrialRunObserver) { + evaluator := &generationEvaluator{ + outDir: outDir, + env: env, + numSpeciesTarget: numSpeciesTarget, + compatAdjustFreq: compatAdjustFreq, + } + return evaluator, evaluator +} + +// TrialRunStarted invoked to notify that new trial run just started. Invoked before any epoch evaluation in that trial run +func (e *generationEvaluator) TrialRunStarted(_ *experiment.Trial) { + // just stub +} + +// TrialRunFinished invoked to notify that the trial run just finished. Invoked after all epochs evaluated or successful solver found. +func (e *generationEvaluator) TrialRunFinished(_ *experiment.Trial) { + // just stub +} + +// EpochEvaluated invoked to notify that evaluation of specific epoch completed. +func (e *generationEvaluator) EpochEvaluated(_ *experiment.Trial, _ *experiment.Generation) { + // just stub +} + +// GenerationEvaluate evaluates a population of organisms and prints their performance on the retina experiment +func (e *generationEvaluator) GenerationEvaluate(ctx context.Context, population *genetics.Population, epoch *experiment.Generation) error { + options, ok := neat.FromContext(ctx) + if !ok { + return neat.ErrNEATOptionsNotFound + } + // Evaluate each organism on a test + var ( + maxPopulationFitness = 0.0 + bestLinkCount = 0 + bestNodeCount = 0 + ) + var bestSubstrateSolver network.Solver + + startTime := time.Now() + for _, organism := range population.Organisms { + isWinner, solver, err := e.organismEvaluate(ctx, organism) + if err != nil { + return err + } + + if organism.Fitness > maxPopulationFitness { + maxPopulationFitness = organism.Fitness + if phenotype, err := organism.Phenotype(); err == nil { + bestLinkCount = phenotype.LinkCount() + bestNodeCount = phenotype.NodeCount() + } else { + neat.ErrorLog(fmt.Sprintf("Failed to get organism Phenotype, reason: %s", err)) + return err + } + bestSubstrateSolver = solver + } + + if isWinner && (epoch.Champion == nil || organism.Fitness > epoch.Champion.Fitness) { + epoch.Solved = true + epoch.WinnerNodes = len(organism.Genotype.Nodes) + epoch.WinnerGenes = organism.Genotype.Extrons() + epoch.WinnerEvals = options.PopSize*epoch.Id + organism.Genotype.Id + epoch.Champion = organism + if epoch.WinnerNodes == 9 { + // You could dump out optimal genomes here if desired + if optPath, err := utils.WriteGenomePlain("retina_optimal", e.outDir, organism, epoch); err != nil { + neat.ErrorLog(fmt.Sprintf("Failed to dump optimal genome, reason: %s\n", err)) + } else { + neat.InfoLog(fmt.Sprintf("Dumped optimal genome to: %s\n", optPath)) + } + } + } + } + elapsedTime := time.Now().Sub(startTime) + + // Fill statistics about current epoch + epoch.FillPopulationStatistics(population) + + // Only print to file every print_every generation + if epoch.Solved || epoch.Id%options.PrintEvery == 0 { + if _, err := utils.WritePopulationPlain(e.outDir, population, epoch); err != nil { + neat.ErrorLog(fmt.Sprintf("Failed to dump population, reason: %s\n", err)) + return err + } + } + + if epoch.Solved { + // print winner organism + org := epoch.Champion + utils.PrintActivationDepth(org, true) + + genomeFile := "retina_cppn_winner" + // Prints the winner organism's Genome to the file! + if orgPath, err := utils.WriteGenomePlain(genomeFile, e.outDir, org, epoch); err != nil { + neat.ErrorLog(fmt.Sprintf("Failed to dump winner organism's genome, reason: %s\n", err)) + } else { + neat.InfoLog(fmt.Sprintf("Generation #%d winner's genome dumped to: %s\n", epoch.Id, orgPath)) + } + + // Dump the winner substrate graph + // + graph := org.Data.Value.(cppn.SubstrateGraphBuilder) + nodes, _ := graph.NodesCount() + edges, _ := graph.EdgesCount() + substrPath := fmt.Sprintf("%s/%s_%d-%d.xml", utils.CreateOutDirForTrial(e.outDir, epoch.TrialId), + "retina_substrate_graph_winner", nodes, edges) + if file, err := os.Create(substrPath); err != nil { + neat.ErrorLog(err.Error()) + } else if err = graph.Marshal(file); err != nil { + neat.ErrorLog(fmt.Sprintf("Failed to dump winner substrate, reason: %s\n", err)) + } else { + neat.InfoLog(fmt.Sprintf("Generation #%d winner's substrate dumped to: %s\n", epoch.Id, substrPath)) + } + } else if epoch.Id < options.NumGenerations-1 { + speciesCount := len(population.Species) + + // adjust species count by keeping it constant + examples.AdjustSpeciesNumber(speciesCount, epoch.Id, e.compatAdjustFreq, e.numSpeciesTarget, options) + + bestSolverLinks, bestSolverNodes := -1, -1 + if bestSubstrateSolver != nil { + bestSolverLinks, bestSolverNodes = bestSubstrateSolver.LinkCount(), bestSubstrateSolver.NodeCount() + } + + neat.InfoLog( + fmt.Sprintf("%d species -> %d organisms [compatibility threshold: %.1f, target: %d]\nbest CPNN organism [fitness: %.2f, links: %d, nodes: %d], best solver [links: %d, nodes: %d], population evaluation time: %v", + speciesCount, len(population.Organisms), options.CompatThreshold, e.numSpeciesTarget, + maxPopulationFitness, bestLinkCount, bestNodeCount, bestSolverLinks, bestSolverNodes, elapsedTime)) + } + return nil +} + +// organismEvaluate evaluates an individual phenotype network with retina experiment and returns true if it's a winner +func (e *generationEvaluator) organismEvaluate(ctx context.Context, organism *genetics.Organism) (bool, network.Solver, error) { + options, ok := eshyperneat.FromContext(ctx) + if !ok { + return false, nil, eshyperneat.ErrESHyperNEATOptionsNotFound + } + // get CPPN phenotype network + cppnSolver, err := organism.Phenotype() + if err != nil { + return false, nil, errors.Wrap(err, "failed to create CPPN solver") + } + + // create substrate layout + inputCount := e.env.inputSize * 2 // left + right pixels of visual object + layout, err := cppn.NewMappedEvolvableSubstrateLayout(inputCount, 2) + if err != nil { + return false, nil, err + } + // create ES-HyperNEAT solver + substr := cppn.NewEvolvableSubstrateWithBias( + layout, options.SubstrateActivator.SubstrateActivationType, options.OutputActivator.OutputActivationType, options.CppnBias) + graph := cppn.NewSubstrateGraphMLBuilder("retina ES-HyperNEAT", false) + createSolverTime := time.Now() + solver, err := substr.CreateNetworkSolver(cppnSolver, graph, options) + if err != nil { + return false, nil, errors.Wrap(err, fmt.Sprintf("failed to evaluate organism: %s", organism)) + } + createSolverElapsedTime := time.Now().Sub(createSolverTime) + + // Evaluate the detector ANN against 256 combinations of the left and the right visual objects + // at correct and incorrect sides of retina + startTime := time.Now() + errorSum, count, detectionErrorCount := 0.0, 0.0, 0.0 + for _, leftObj := range e.env.visualObjects { + for _, rightObj := range e.env.visualObjects { + // Evaluate outputted predictions + loss, err := evaluateNetwork(solver, leftObj, rightObj) + if err != nil { + return false, nil, err + } + errorSum += loss + count += 1.0 + if loss > 0 { + detectionErrorCount += 1.0 + } + // flush solver + if flushed, err := solver.Flush(); err != nil { + return false, nil, err + } else if !flushed { + return false, nil, errors.New("failed to flush solver after evaluation") + } + } + } + elapsed := time.Since(startTime) + + // Calculate the fitness score + fitness := maxFitness / (1.0 + errorSum) + avgError := errorSum / count + + isWinner := false + if fitness >= fitnessThreshold { + isWinner = true + fmt.Printf("Found a Winner! \n") + // save solver graph to the winner organism + organism.Data = &genetics.OrganismData{Value: graph} + } + // Save properties to organism struct + organism.IsWinner = isWinner + organism.Error = avgError + organism.Fitness = fitness + + if debug { + neat.InfoLog(fmt.Sprintf("Average error: %f, errors sum: %f, fitness: %f, false detections: %.0f from: %.0f", + avgError, errorSum, fitness, detectionErrorCount, count)) + neat.InfoLog(fmt.Sprintf("Substrate: nodes = %d, edges = %d | CPPN phenotype: nodes = %d, edges = %d", + solver.NodeCount(), solver.LinkCount(), cppnSolver.NodeCount(), cppnSolver.LinkCount())) + neat.InfoLog(fmt.Sprintf("Substrate: evaluation time = %v, create solver time = %v", elapsed, createSolverElapsedTime)) + } + + return isWinner, solver, nil +} + +// evaluateNetwork is to evaluate provided network solver using provided visual objects to test prediction performance. +// Returns the prediction loss value or error if failed to evaluate. +func evaluateNetwork(solver network.Solver, leftObj VisualObject, rightObj VisualObject) (float64, error) { + // flush current network state + if _, err := solver.Flush(); err != nil { + return -1, err + } + + // Create input by joining data from left and right visual objects + inputs := append(leftObj.data, rightObj.data...) + + // run evaluation + loss := maxFitness + if err := solver.LoadSensors(inputs); err != nil { + return loss, err + } + + // Propagate activation + activationSteps := 1000 + if relaxed, err := solver.Relax(activationSteps, 0.1); err != nil { + return loss, err + } else if !relaxed { + neat.DebugLog("failed to relax network solver of the ES substrate") + return loss, nil + } + + // get outputs and evaluate against ground truth + outs := solver.ReadOutputs() + loss = evaluatePredictions(outs, leftObj, rightObj) + return loss, nil +} + +// evaluatePredictions returns the loss between predictions and ground truth of leftObj and rightObj +func evaluatePredictions(predictions []float64, leftObj VisualObject, rightObj VisualObject) float64 { + // Convert predictions[i] to 1.0 or 0.0 about 0.5 threshold + normPredictions := make([]float64, len(predictions)) + for i := 0; i < len(normPredictions); i++ { + if predictions[i] >= 0.5 { + normPredictions[i] = 1.0 + } else { + predictions[i] = 0.0 + } + } + + // Get ground truth values + targets := make([]float64, 2) + + // Set target[0] to 1.0 if LeftObj is suitable for Left side, otherwise set to 0.0 + if leftObj.Side == LeftSide || leftObj.Side == BothSide { + targets[0] = 1.0 + } else { + targets[0] = 0.0 + } + + // Repeat for target[1], the right side truth value + if rightObj.Side == RightSide || rightObj.Side == BothSide { + targets[1] = 1.0 + } else { + targets[1] = 0.0 + } + + // Find normalized loss + normLoss := (normPredictions[0]-targets[0])*(normPredictions[0]-targets[0]) + (normPredictions[1]-targets[1])*(normPredictions[1]-targets[1]) + if normLoss == 0 { + return 0.0 + } + + // find loss as item-wise difference between two vectors + loss := (math.Abs(predictions[0]-targets[0]) + math.Abs(predictions[1]-targets[1])) / 2.0 + + neat.DebugLog(fmt.Sprintf("[%.2f, %.2f] -> [%.2f, %.2f] loss: %.2f", + targets[0], targets[1], normPredictions[0], normPredictions[1], normLoss)) + + return loss +} diff --git a/experiments/retina/retina_test.go b/examples/retina/retina_test.go similarity index 69% rename from experiments/retina/retina_test.go rename to examples/retina/retina_test.go index 756bd1b..66d1a19 100644 --- a/experiments/retina/retina_test.go +++ b/examples/retina/retina_test.go @@ -10,8 +10,8 @@ func Test_evaluatePredictions(t *testing.T) { sumLoss := 0.0 for _, leftObj := range dataset { for _, rightObj := range dataset { - sumLoss += evaluatePredictions([]float64{0, 0, 0, 0}, leftObj, rightObj) + sumLoss += evaluatePredictions([]float64{0, 0}, leftObj, rightObj) } } - assert.Equal(t, 416.0, sumLoss) + assert.Equal(t, 208.0, sumLoss) } diff --git a/executor.go b/executor.go index 7430d0a..638c924 100644 --- a/executor.go +++ b/executor.go @@ -1,64 +1,59 @@ package main import ( + "context" "flag" "fmt" + "github.com/yaricom/goESHyperNEAT/v2/eshyperneat" + "github.com/yaricom/goESHyperNEAT/v2/examples/retina" + "github.com/yaricom/goNEAT/v4/experiment" + "github.com/yaricom/goNEAT/v4/neat" + "github.com/yaricom/goNEAT/v4/neat/genetics" "log" "math/rand" "os" - "strings" + "os/signal" + "syscall" "time" - - "github.com/yaricom/goESHyperNEAT/eshyperneat" - "github.com/yaricom/goESHyperNEAT/experiments/retina" - "github.com/yaricom/goNEAT/experiments" - "github.com/yaricom/goNEAT/neat" - "github.com/yaricom/goNEAT/neat/genetics" ) -// The experiment runner boilerplate code +// The experiment runner code func main() { var outDirPath = flag.String("out", "./out", "The output directory to store results.") var contextPath = flag.String("context", "./data/retina/es_hyper.neat.yml", "The execution context configuration file.") var genomePath = flag.String("genome", "./data/retina/cppn_genome.yml", "The seed genome to start with.") var experimentName = flag.String("experiment", "retina", "The name of experiment to run. [retina]") + var speciesTarget = flag.Int("species_target", 15, "The target number of species to maintain.") + var speciesCompatAdjustFreq = flag.Int("species_adjust_freq", 10, "The frequency of species compatibility threshold adjustments when trying to maintain their number.") - var logLevel = flag.Int("log-level", -1, "The logger level to be used. Overrides the one set in configuration.") + var logLevel = flag.String("log-level", "", "The logger level to be used. Overrides the one set in configuration.") var trialsCount = flag.Int("trials", 0, "The number of trials for experiment. Overrides the one set in configuration.") + var seed = flag.Int64("seed", -1, "The seed for the random number generator [-1 to use current Unix timestamp].") flag.Parse() // Seed the random-number generator with current time so that // the numbers will be different every time we run. - rand.Seed(time.Now().Unix()) + if *seed < 0 { + *seed = time.Now().UnixNano() + } + rand.Seed(*seed) // Load context configuration - configFile, err := os.Open(*contextPath) + neatOptions, err := neat.ReadNeatOptionsFromFile(*contextPath) if err != nil { - log.Fatal("Failed to open context configuration file: ", err) - } - context, err := eshyperneat.Load(configFile) - if err != nil { - log.Fatal("Failed to load context from config file: ", err) + log.Fatal("Failed to load NEAT options: ", err) } // Load Genome log.Printf("Loading start genome for %s experiment\n", *experimentName) - genomeFile, err := os.Open(*genomePath) - if err != nil { - log.Fatal("Failed to open genome file: ", err) - } - encoding := genetics.PlainGenomeEncoding - if strings.HasSuffix(*genomePath, ".yml") { - encoding = genetics.YAMLGenomeEncoding - } - decoder, err := genetics.NewGenomeReader(genomeFile, encoding) + reader, err := genetics.NewGenomeReaderFromFile(*genomePath) if err != nil { - log.Fatal("Failed to create genome decoder: ", err) + log.Fatalf("Failed to open genome file, reason: '%s'", err) } - startGenome, err := decoder.Read() + startGenome, err := reader.Read() if err != nil { - log.Fatal("Failed to read start genome of CPPN: ", err) + log.Fatalf("Failed to read start genome, reason: '%s'", err) } fmt.Println(startGenome) @@ -66,7 +61,7 @@ func main() { // Check if output dir exists outDir := *outDirPath - if _, err := os.Stat(outDir); err == nil { + if _, err = os.Stat(outDir); err == nil { // backup it backUpDir := fmt.Sprintf("%s-%s", outDir, time.Now().Format("2006-01-02T15_04_05")) // clear it @@ -83,37 +78,95 @@ func main() { // Override context configuration parameters with ones set from command line if *trialsCount > 0 { - context.NumRuns = *trialsCount + neatOptions.NumRuns = *trialsCount } - if *logLevel >= 0 { + if len(*logLevel) > 0 { neat.LogLevel = neat.LoggerLevel(*logLevel) } - // Create Retina Experiment - experiment := experiments.Experiment{ - Id: 0, - Trials: make(experiments.Trials, context.NumRuns), + // Create Experiment + experimentContext := neatOptions.NeatContext() + exp := experiment.Experiment{ + Id: 0, + Trials: make(experiment.Trials, neatOptions.NumRuns), + RandSeed: *seed, } - var generationEvaluator experiments.GenerationEvaluator + var generationEvaluator experiment.GenerationEvaluator + var trialObserver experiment.TrialRunObserver switch *experimentName { case "retina": - if env, err := retina.NewRetinaEnvironment(retina.CreateRetinaDataset(), 4, context); err != nil { + opts, err := eshyperneat.LoadYAMLConfigFile(*contextPath) + if err != nil { + log.Fatal("Failed to load ES-HyperNEAT options from config file: ", err) + } else { + experimentContext = eshyperneat.NewContext(experimentContext, opts) + } + if env, err := retina.NewRetinaEnvironment(retina.CreateRetinaDataset(), 4); err != nil { log.Fatalf("Failed to create retina environment, reason: %s", err) } else { - generationEvaluator = retina.NewGenerationEvaluator(*outDirPath, env) + generationEvaluator, trialObserver = retina.NewGenerationEvaluator( + *outDirPath, env, *speciesTarget, *speciesCompatAdjustFreq) } default: log.Fatalf("Unsupported experiment name requested: %s\n", *experimentName) } - fmt.Println("Done Setup, Starting Experiment!") + // prepare to execute + errChan := make(chan error) + ctx, cancel := context.WithCancel(experimentContext) - err = experiment.Execute(context.NeatContext, startGenome, generationEvaluator) + // run experiment in the separate GO routine + go func() { + if err = exp.Execute(ctx, startGenome, generationEvaluator, trialObserver); err != nil { + errChan <- err + } else { + errChan <- nil + } + }() + + // register handler to wait for termination signals + // + go func(cancel context.CancelFunc) { + fmt.Println("\nPress Ctrl+C to stop") + + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + select { + case <-signals: + // signal to stop test fixture + cancel() + case err = <-errChan: + // stop waiting + } + }(cancel) + + // Wait for experiment completion + // + err = <-errChan if err != nil { - neat.ErrorLog(fmt.Sprintf("Failed to perform %s experiment! Reason: %s\n", *experimentName, err)) - os.Exit(1) + // error during execution + log.Fatalf("Experiment execution failed: %s", err) } - // Output winner statistics - experiment.PrintStatistics() + // Print experiment results statistics + // + exp.PrintStatistics() + + // Save experiment data in native format + // + expResPath := fmt.Sprintf("%s/%s.dat", outDir, *experimentName) + if expResFile, err := os.Create(expResPath); err != nil { + log.Fatal("Failed to create file for experiment results", err) + } else if err = exp.Write(expResFile); err != nil { + log.Fatal("Failed to save experiment results", err) + } + + // Save experiment data in Numpy NPZ format if requested + // + npzResPath := fmt.Sprintf("%s/%s.npz", outDir, *experimentName) + if npzResFile, err := os.Create(npzResPath); err != nil { + log.Fatalf("Failed to create file for experiment results: [%s], reason: %s", npzResPath, err) + } else if err = exp.WriteNPZ(npzResFile); err != nil { + log.Fatal("Failed to save experiment results as NPZ file", err) + } } diff --git a/experiments/retina/cppn_network_builder.go b/experiments/retina/cppn_network_builder.go deleted file mode 100644 index 0ea7ecc..0000000 --- a/experiments/retina/cppn_network_builder.go +++ /dev/null @@ -1,67 +0,0 @@ -package retina - -import ( - "fmt" - - "github.com/yaricom/goESHyperNEAT/cppn" - "github.com/yaricom/goESHyperNEAT/eshyperneat" - "github.com/yaricom/goNEAT/neat/genetics" - "github.com/yaricom/goNEAT/neat/network" - "github.com/yaricom/goNEAT/neat/utils" -) - -const debug = true - -// CPPNNetworkBuilder handles the building of Network's using CPPN network's to query connections/topology by the esHyperNEAT algorithm -type CPPNNetworkBuilder struct { - UseLEO bool - SubstrateLayout cppn.EvolvableSubstrateLayout - NodesActivation utils.NodeActivationType - Context *eshyperneat.ESHyperNEATContext - - // New Graph Builder generator function. - // Note: Graph Builder is used to store graphs in a user-friendly format - NewGraphBuilder func() *cppn.SubstrateGraphBuilder -} - -//CreateANNFromCPPNOrganism creates a NetworkSolver (ANN) by querying the Organism.Phenotype cppnNetwork by the ESHyperNeat Algorithm -func (builder *CPPNNetworkBuilder) CreateANNFromCPPNOrganism(cppnOrganism *genetics.Organism) (network.NetworkSolver, error) { - return builder.CreateANNFromCPPN(cppnOrganism.Phenotype) -} - -//CreateANNFromCPPN creates a NetworkSolver (ANN) by querying the cppnNetwork by the ESHyperNeat Algorithm -func (builder *CPPNNetworkBuilder) CreateANNFromCPPN(cppnNetwork *network.Network) (network.NetworkSolver, error) { - // create substrate which will be connected to form the network - substrate := cppn.NewEvolvableSubstrate(builder.SubstrateLayout, builder.NodesActivation) - cppnFastNetwork, err := cppnNetwork.FastNetworkSolver() - if err != nil { - return nil, err - } - - // create a new graphBuilder to store the graph in a graphable and user-friendly format - graphml xml - graphBuilder := builder.NewGraphBuilder() - - // unwrap graphBuilder. graphBuilder may not be provided (its nil) - var annSolver network.NetworkSolver - if graphBuilder == nil { - // create our ann by querying the cppn network and applying the eshypernet quadtree/pruning/banding algorithm - annSolver, err = substrate.CreateNetworkSolver( - cppnFastNetwork, nil, builder.Context, - ) - } else { - // create our ann by querying the cppn network and applying the eshypernet quadtree/pruning/banding algorithm - annSolver, err = substrate.CreateNetworkSolver( - cppnFastNetwork, *graphBuilder, builder.Context, - ) - } - if err != nil { - return nil, err - } - - if debug { - fmt.Print("CPPN: ", cppnFastNetwork, "\n") - fmt.Print("Generated ANN: ", annSolver, "\n\n") - } - - return annSolver, nil -} diff --git a/experiments/retina/retina.go b/experiments/retina/retina.go deleted file mode 100644 index 9f5e225..0000000 --- a/experiments/retina/retina.go +++ /dev/null @@ -1,255 +0,0 @@ -// Package retina provides implementation of the retina experiment -package retina - -import ( - "errors" - "fmt" - "github.com/yaricom/goESHyperNEAT/cppn" - "github.com/yaricom/goNEAT/experiments" - "github.com/yaricom/goNEAT/neat" - "github.com/yaricom/goNEAT/neat/genetics" - "github.com/yaricom/goNEAT/neat/network" - "math" - "os" -) - -const ( - // maxFitness Used as max value which we add error too to get an organism's fitness - maxFitness = 1000.0 - // fitnessThreshold is the fitness value for which an organism is considered to have won the experiment - fitnessThreshold = maxFitness -) - -type generationEvaluator struct { - outDir string - env *Environment -} - -// NewGenerationEvaluator is to create new generations evaluator for retina experiment. -func NewGenerationEvaluator(outDir string, env *Environment) experiments.GenerationEvaluator { - return &generationEvaluator{ - outDir: outDir, - env: env, - } -} - -// GenerationEvaluate evaluates a population of organisms and prints their performance on the retina experiment -func (e *generationEvaluator) GenerationEvaluate(population *genetics.Population, epoch *experiments.Generation, context *neat.NeatContext) (err error) { - // Evaluate each organism on a test - for idx, organism := range population.Organisms { - isWinner, err := e.organismEvaluate(organism) - if err != nil { - return err - } - neat.InfoLog(fmt.Sprintf("organism #%d fitness %f \n", idx, organism.Fitness)) - - if isWinner && (epoch.Best == nil || organism.Fitness > epoch.Best.Fitness) { - epoch.Solved = true - epoch.WinnerNodes = len(organism.Genotype.Nodes) - epoch.WinnerGenes = organism.Genotype.Extrons() - epoch.WinnerEvals = context.PopSize*epoch.Id + organism.Genotype.Id - epoch.Best = organism - if epoch.WinnerNodes == 9 { - // You could dump out optimal genomes here if desired - optPath := fmt.Sprintf("%s/%s_%d-%d", experiments.OutDirForTrial(e.outDir, epoch.TrialId), - "retina_optimal", organism.Phenotype.NodeCount(), organism.Phenotype.LinkCount()) - file, err := os.Create(optPath) - if err != nil { - neat.ErrorLog(fmt.Sprintf("Failed to dump optimal genome, reason: %s\n", err)) - } else if err = organism.Genotype.Write(file); err != nil { - neat.ErrorLog("Failed to save optimal genotype") - } else { - neat.InfoLog(fmt.Sprintf("Dumped optimal genome to: %s\n", optPath)) - } - } - } - } - - // Fill statistics about current epoch - epoch.FillPopulationStatistics(population) - - // Only print to file every print_every generations - if epoch.Solved || epoch.Id%context.PrintEvery == 0 { - popPath := fmt.Sprintf("%s/gen_%d", experiments.OutDirForTrial(e.outDir, epoch.TrialId), epoch.Id) - file, err := os.Create(popPath) - if err != nil { - neat.ErrorLog(fmt.Sprintf("Failed to dump population, reason: %s\n", err)) - } else { - population.WriteBySpecies(file) - } - } - - if epoch.Solved { - // print winner organism - for _, org := range population.Organisms { - if org.IsWinner { - // Prints the winner CPPN organism to file! - // - orgPath := fmt.Sprintf("%s/%s_%d-%d", experiments.OutDirForTrial(e.outDir, epoch.TrialId), - "retina_cppn_winner", org.Phenotype.NodeCount(), org.Phenotype.LinkCount()) - if file, err := os.Create(orgPath); err != nil { - neat.ErrorLog(err.Error()) - } else if err = org.Genotype.Write(file); err != nil { - neat.ErrorLog(fmt.Sprintf("Failed to dump winner CPPN genotype, reason: %s\n", err)) - } else { - neat.InfoLog(fmt.Sprintf("Generation #%d winner CPPN dumped to: %s\n", epoch.Id, orgPath)) - } - // Dump the winner substrate graph - // - graph := org.Data.Value.(cppn.SubstrateGraphBuilder) - nodes, _ := graph.NodesCount() - edges, _ := graph.EdgesCount() - substrPath := fmt.Sprintf("%s/%s_%d-%d.xml", experiments.OutDirForTrial(e.outDir, epoch.TrialId), - "retina_substrate_graph_winner", nodes, edges) - if file, err := os.Create(substrPath); err != nil { - neat.ErrorLog(err.Error()) - } else if err = graph.Marshal(file); err != nil { - neat.ErrorLog(fmt.Sprintf("Failed to dump winner substrate, reason: %s\n", err)) - } else { - neat.InfoLog(fmt.Sprintf("Generation #%d winner's substrate dumped to: %s\n", epoch.Id, substrPath)) - } - break - } - } - } - return err -} - -// organismEvaluate evaluates an individual phenotype network with retina experiment and returns true if its won -func (e generationEvaluator) organismEvaluate(organism *genetics.Organism) (bool, error) { - // get CPPN network solver - cppnSolver, err := organism.Phenotype.FastNetworkSolver() - if err != nil { - return false, err - } - - // create substrate layout - inputCount := e.env.inputSize * 2 // left + right pixels of visual object - layout, err := cppn.NewMappedEvolvableSubstrateLayout(inputCount, 2) - if err != nil { - return false, err - } - // create ES-HyperNEAT solver - substr := cppn.NewEvolvableSubstrate(layout, e.env.context.SubstrateActivator) - graph := cppn.NewSubstrateGraphMLBuilder("retina ES-HyperNEAT", false) - solver, err := substr.CreateNetworkSolver(cppnSolver, graph, e.env.context) - if err != nil { - return false, err - } - - // Evaluate the detector ANN against 256 combinations of the left and the right visual objects - // at correct and incorrect sides of retina - errorSum, count, detectionErrorCount := 0.0, 0.0, 0.0 - for _, leftObj := range e.env.visualObjects { - for _, rightObj := range e.env.visualObjects { - // Evaluate outputted predictions - loss, err := evaluateNetwork(solver, leftObj, rightObj) - if err != nil { - return false, err - } - errorSum += loss - count += 1.0 - if loss > 0 { - detectionErrorCount += 1.0 - } - // flush solver - if flushed, err := solver.Flush(); err != nil { - return false, err - } else if !flushed { - return false, errors.New("failed to flush solver after evaluation") - } - } - } - - // Calculate the fitness score - fitness := maxFitness / (1.0 + errorSum) - avgError := errorSum / count - - neat.InfoLog(fmt.Sprintf("Average error: %f, errors sum: %f, false detections: %f from: %f", - avgError, errorSum, detectionErrorCount, count)) - nodes, _ := graph.NodesCount() - edges, _ := graph.EdgesCount() - neat.InfoLog(fmt.Sprintf("Substrate: #nodes = %d, #edges = %d | CPPN phenotype: #nodes = %d, #edges = %d", - nodes, edges, cppnSolver.NodeCount(), cppnSolver.LinkCount())) - - isWinner := false - if organism.Fitness > fitnessThreshold { - isWinner = true - fmt.Printf("Found a Winner! \n") - // save solver graph to the winner organism - organism.Data = &genetics.OrganismData{Value: graph} - } - // Save properties to organism struct - organism.IsWinner = isWinner - organism.Error = avgError - organism.Fitness = fitness - - return organism.IsWinner, nil -} - -// evaluateNetwork is to evaluate provided network solver using provided visual objects to test prediction performance. -// Returns the prediction loss value or error if failed to evaluate. -func evaluateNetwork(solver network.NetworkSolver, leftObj VisualObject, rightObj VisualObject) (float64, error) { - // Create input by joining data from left and right visual objects - inputs := append(leftObj.data, rightObj.data...) - - // run evaluation - loss := math.MaxFloat64 - if err := solver.LoadSensors(inputs); err != nil { - return loss, err - } - if relaxed, err := solver.RecursiveSteps(); err != nil { - return loss, err - } else if !relaxed { - return loss, errors.New("failed to relax network solver of the ES substrate") - } - - // get outputs and evaluate against ground truth - outs := solver.ReadOutputs() - loss = evaluatePredictions(outs, leftObj, rightObj) - return loss, nil -} - -// evaluatePredictions returns the loss between predictions and ground truth of leftObj and rightObj -func evaluatePredictions(predictions []float64, leftObj VisualObject, rightObj VisualObject) float64 { - // Convert predictions[i] to 1.0 or 0.0 about 0.5 threshold - normPreds := make([]float64, len(predictions)) - for i := 0; i < len(normPreds); i++ { - if normPreds[i] >= 0.5 { - normPreds[i] = 1.0 - } else { - normPreds[i] = 0.0 - } - } - - // Get ground truth values - targets := make([]float64, 2) - - // Set target[0] to 1.0 if LeftObj is suitable for Left side, otherwise set to 0.0 - if leftObj.Side == LeftSide || leftObj.Side == BothSide { - targets[0] = 1.0 - } else { - targets[0] = 0.0 - } - - // Repeat for target[1], the right side truth value - if rightObj.Side == RightSide || rightObj.Side == BothSide { - targets[1] = 1.0 - } else { - targets[1] = 0.0 - } - - // Find loss as an Euclidean distance between outputs and ground truth - loss := (normPreds[0]-targets[0])*(normPreds[0]-targets[0]) + (normPreds[1]-targets[1])*(normPreds[1]-targets[1]) - - flag := "match" - if loss != 0 { - flag = "-" - } - - neat.DebugLog(fmt.Sprintf("[%.2f, %.2f] -> [%.2f, %.2f] '%s'", - targets[0], targets[1], normPreds[0], normPreds[1], flag)) - - return loss - -} diff --git a/go.mod b/go.mod index ee4cc4c..7591d84 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,20 @@ -module github.com/yaricom/goESHyperNEAT +module github.com/yaricom/goESHyperNEAT/v2 -go 1.15 +go 1.21 require ( github.com/pkg/errors v0.9.1 - github.com/spf13/viper v1.7.1 - github.com/stretchr/testify v1.7.0 - github.com/yaricom/goGraphML v1.1.0 - github.com/yaricom/goNEAT v0.0.0-20210317183214-058a86c8043f + github.com/stretchr/testify v1.10.0 + github.com/yaricom/goGraphML v1.4.3 + github.com/yaricom/goNEAT/v4 v4.2.0 + gonum.org/v1/gonum v0.14.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sbinet/npyio v0.8.0 // indirect + github.com/spf13/cast v1.5.1 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect ) diff --git a/go.sum b/go.sum index 49a3122..6667a7d 100644 --- a/go.sum +++ b/go.sum @@ -1,353 +1,44 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY= -github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.5.1 h1:VHu76Lk0LSP1x254maIu2bplkWpfBWI+B+6fdoZprcg= -github.com/spf13/afero v1.5.1/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= -github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sbinet/npyio v0.8.0 h1:n+jtLFIjcJNENOI44lG7BUwWFqtgdQAerqyXDtC956A= +github.com/sbinet/npyio v0.8.0/go.mod h1:26fj1nEFY78AYqkANkcSw4dYwLHXFWFehgOSu5HKwgw= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/vdyagilev/goESHyperNEAT v0.0.0-20210320182635-8d87d8fe18a7 h1:mbACIIXEsYVqCO1K2AhYNRMVfdhu50tEMZG+SANTM/4= -github.com/vdyagilev/goESHyperNEAT v0.0.0-20210320182635-8d87d8fe18a7/go.mod h1:Tt2ks3HZwGkj5OOTviAYMHicWf62aqNKs6zprYEkr8c= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yaricom/goESHyperNEAT v0.0.0-20210315173628-f56070748916 h1:zSs84m58kO5SRbiW/xDhLgK6dveO/enp9Mb/IeM8jDk= -github.com/yaricom/goESHyperNEAT v0.0.0-20210315173628-f56070748916/go.mod h1:imok/JvhE9Y/9zBSo6gk6wB2eR/DMcsb+cUU07/haaY= -github.com/yaricom/goGraphML v0.0.0-20190109114128-b4665c578dfe h1:hZ3RTFDtNZpOHNKoyMY4yW6D00v44P0pXzhlG1MQdZw= -github.com/yaricom/goGraphML v0.0.0-20190109114128-b4665c578dfe/go.mod h1:brbnKEQs1Fw8GF0wuypX6DObHS9S/nGQv6jul8ZrWVI= -github.com/yaricom/goGraphML v1.1.0 h1:CrM6yGmZ8Azv2Id2KIzei277MPe5YFzKkOOJu45uOBM= -github.com/yaricom/goGraphML v1.1.0/go.mod h1:OM0MGAy6tdufwNYPW9BS2mR6NMArD7RtlakyTs+A3Vk= -github.com/yaricom/goNEAT v0.0.0-20190822164653-2553ade85ca4 h1:eUAy6gCm5s6bfGZTFZgYq7VcUwqdBchFAVQP39EcaYc= -github.com/yaricom/goNEAT v0.0.0-20190822164653-2553ade85ca4/go.mod h1:isxHmBMXtsc0Sl6KrQuU6VXFMx+un56M5FSwyLRXEB4= -github.com/yaricom/goNEAT v0.0.0-20210317183214-058a86c8043f h1:PUjzjQbFUIiB8mO+VAH+Udg9zQqZmVbve15imziMwGg= -github.com/yaricom/goNEAT v0.0.0-20210317183214-058a86c8043f/go.mod h1:GITpkyU810UM9mzboL5bpOuod7WWqehlsrY+xZ1VdlY= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e h1:XNp2Flc/1eWQGk5BLzqTAN7fQIwIbfyVTuVxXxZh73M= -golang.org/x/sys v0.0.0-20210317225723-c4fcb01b228e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yaricom/goGraphML v1.4.3 h1:8S8Q7zH56Ot4owhMX6ElgHFxgDlaHDXJZf4P1vm1eB8= +github.com/yaricom/goGraphML v1.4.3/go.mod h1:WdO/4yppeN4Ly/dnr5/Z+d/1UoQ8SA8r+x0IQ6/jBbE= +github.com/yaricom/goNEAT/v4 v4.2.0 h1:ms7Tjg//rl54SbkpML6KbWmDXJul04sqPxc3zBFIuy8= +github.com/yaricom/goNEAT/v4 v4.2.0/go.mod h1:/tekyUuX/P14WrL5aL/kgbDE4rB/rRJtpCS8OSp4UB4= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hyperneat/context.go b/hyperneat/context.go new file mode 100644 index 0000000..8aecf96 --- /dev/null +++ b/hyperneat/context.go @@ -0,0 +1,23 @@ +package hyperneat + +import "context" + +// key is an unexported type for keys defined in this package. +// This prevents collisions with keys defined in other packages. +type key int + +// hyperNeatOptionsKey is the key for hyperneat.Options value in Contexts. It is +// unexported; clients use hyperneat.NewContext and hyperneat.FromContext +// instead of using this key directly. +var hyperNeatOptionsKey key + +// NewContext returns a new Context that carries value of HyperNEAT options. +func NewContext(ctx context.Context, opts *Options) context.Context { + return context.WithValue(ctx, hyperNeatOptionsKey, opts) +} + +// FromContext returns the HyperNEAT Options value stored in ctx, if any. +func FromContext(ctx context.Context) (*Options, bool) { + u, ok := ctx.Value(hyperNeatOptionsKey).(*Options) + return u, ok +} diff --git a/hyperneat/hyper_neat.go b/hyperneat/hyper_neat.go index 55e6e5e..e52b4ac 100644 --- a/hyperneat/hyper_neat.go +++ b/hyperneat/hyper_neat.go @@ -1,70 +1,79 @@ -// The package hyperneat holds implementation of HyperNEAT family of algorithms, including Evolvable-Substrate HyperNEAT +// Package hyperneat holds implementation of HyperNEAT family of algorithms package hyperneat import ( - "bytes" - "errors" + "github.com/pkg/errors" + "github.com/yaricom/goNEAT/v4/neat/math" + "gopkg.in/yaml.v3" "io" - - "github.com/spf13/viper" - "github.com/yaricom/goNEAT/neat" - "github.com/yaricom/goNEAT/neat/utils" + "os" ) -// The HyperNEAT execution context -type HyperNEATContext struct { - // The NEAT context included - *neat.NeatContext +type SubstrateActivatorType struct { + SubstrateActivationType math.NodeActivationType +} + +type OutputActivatorType struct { + OutputActivationType math.NodeActivationType +} - // The threshold value to indicate which links should be included - LinkThreshold float64 - // The weight range defines the minimum and maximum values for weights on substrate connections, they go +// Options The HyperNEAT execution options +type Options struct { + // LinkThreshold The threshold value to indicate which links should be included + LinkThreshold float64 `yaml:"link_threshold"` + // WeightRange The weight range defines the minimum and maximum values for weights on substrate connections, they go // from -WeightRange to +WeightRange, and can be any integer - WeightRange float64 + WeightRange float64 `yaml:"weight_range"` - // The substrate activation function - SubstrateActivator utils.NodeActivationType -} + // LeoEnabled flag to control if Link Expression Output (LEO) enabled + LeoEnabled bool `yaml:"leo_enabled"` -// Load is to read HyperNEAT context options from the provided reader -func Load(r io.Reader) (*HyperNEATContext, error) { - var buff bytes.Buffer - tee := io.TeeReader(r, &buff) + // SubstrateActivator The activation function for the hidden substrate nodes + SubstrateActivator SubstrateActivatorType `yaml:"substrate_activator"` + // OutputActivatorType The activation function for the output substrate nodes + OutputActivator OutputActivatorType `yaml:"output_activator"` - // NEAT context loading - nCtx := &neat.NeatContext{} - if err := nCtx.LoadContext(tee); err != nil { - return nil, err - } + // CppnBias The BIAS value for CPPN network + CppnBias float64 `yaml:"cppn_bias,omitempty"` +} - // Load HyperNEAT options - ctx := &HyperNEATContext{NeatContext: nCtx} - if err := ctx.load(&buff); err != nil { +// LoadYAMLOptions is to read HyperNEAT options from the provided reader +func LoadYAMLOptions(r io.Reader) (*Options, error) { + content, err := io.ReadAll(r) + if err != nil { return nil, err } - return ctx, nil + // read options + var opts Options + if err = yaml.Unmarshal(content, &opts); err != nil { + return nil, errors.Wrap(err, "failed to decode HyperNEAT options from YAML") + } + return &opts, nil } -// Loads only HyperNEAT context from provided configuration data -func (h *HyperNEATContext) load(r io.Reader) error { - viper.SetConfigType("YAML") - err := viper.ReadConfig(r) +// LoadYAMLConfigFile is to load ES-HyperNEAT options from provided configuration file +func LoadYAMLConfigFile(path string) (*Options, error) { + configFile, err := os.Open(path) if err != nil { - return err + return nil, errors.Wrap(err, "failed to open HyperNEAT configuration file") } - v := viper.Sub("hyperneat") - if v == nil { - return errors.New("hyperneat subsection not found in configuration") - } - - h.LinkThreshold = v.GetFloat64("link_threshold") - h.WeightRange = v.GetFloat64("weight_range") + return LoadYAMLOptions(configFile) +} - // read substrate activator - subAct := v.GetString("substrate_activator") - if h.SubstrateActivator, err = utils.NodeActivators.ActivationTypeFromName(subAct); err != nil { - return err +func (s *SubstrateActivatorType) UnmarshalYAML(value *yaml.Node) error { + if activationType, err := math.NodeActivators.ActivationTypeFromName(value.Value); err != nil { + return errors.Wrap(err, "failed to decode substrate activator function from HyperNEAT options") + } else { + s.SubstrateActivationType = activationType } + return nil +} +func (o *OutputActivatorType) UnmarshalYAML(value *yaml.Node) error { + if activationType, err := math.NodeActivators.ActivationTypeFromName(value.Value); err != nil { + return errors.Wrap(err, "failed to decode output activator function from HyperNEAT options") + } else { + o.OutputActivationType = activationType + } return nil } diff --git a/hyperneat/hyper_neat_test.go b/hyperneat/hyper_neat_test.go index ea0f1ce..b25ee86 100644 --- a/hyperneat/hyper_neat_test.go +++ b/hyperneat/hyper_neat_test.go @@ -3,20 +3,35 @@ package hyperneat import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/yaricom/goNEAT/neat/utils" + "github.com/yaricom/goNEAT/v4/neat/math" "os" "testing" ) -func TestHyperNEATContext_LoadContext(t *testing.T) { - r, err := os.Open("../data/test/test_es_hyper.neat.yml") +const hyperNeatCfgPath = "../data/test/test_hyper.neat.yml" + +func TestLoadYAMLOptions(t *testing.T) { + r, err := os.Open(hyperNeatCfgPath) require.NoError(t, err, "failed to open config file") - ctx, err := Load(r) - require.NoError(t, err, "failed to load context") + opts, err := LoadYAMLOptions(r) + require.NoError(t, err, "failed to load HyperNEAT options") // check values - assert.Equal(t, utils.SigmoidSteepenedActivation, ctx.SubstrateActivator) - assert.Equal(t, 0.2, ctx.LinkThreshold) - assert.Equal(t, 3.0, ctx.WeightRange) + checkHyperNeatOptions(opts, t) +} + +func TestLoadYAMLConfigFile(t *testing.T) { + opts, err := LoadYAMLConfigFile(hyperNeatCfgPath) + require.NoError(t, err, "failed to load HyperNEAT options") + + // check values + checkHyperNeatOptions(opts, t) +} + +func checkHyperNeatOptions(opts *Options, t *testing.T) { + assert.Equal(t, math.SigmoidSteepenedActivation, opts.SubstrateActivator.SubstrateActivationType) + assert.Equal(t, math.SigmoidPlainActivation, opts.OutputActivator.OutputActivationType) + assert.Equal(t, 0.2, opts.LinkThreshold) + assert.Equal(t, 3.0, opts.WeightRange) }