From 342646392fb940e466d0cf499bddeeed16bd000a Mon Sep 17 00:00:00 2001 From: Iaroslav Omelianenko Date: Fri, 19 Apr 2024 21:16:40 +0300 Subject: [PATCH 1/8] Implemented serialization of the `FastModularNetworkSolver`. Implemented related test cases. --- neat/network/fast_network.go | 9 +-- neat/network/fast_network_model_io.go | 77 ++++++++++++++++++++++ neat/network/fast_network_model_io_test.go | 56 ++++++++++++++++ 3 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 neat/network/fast_network_model_io.go create mode 100644 neat/network/fast_network_model_io_test.go diff --git a/neat/network/fast_network.go b/neat/network/fast_network.go index b67b331..a0017d8 100644 --- a/neat/network/fast_network.go +++ b/neat/network/fast_network.go @@ -10,13 +10,13 @@ import ( // FastNetworkLink The connection descriptor for fast network type FastNetworkLink struct { // The index of source neuron - SourceIndex int + SourceIndex int `json:"source_index"` // The index of target neuron - TargetIndex int + TargetIndex int `json:"target_index"` // The weight of this link - Weight float64 + Weight float64 `json:"weight"` // The signal relayed by this link - Signal float64 + Signal float64 `json:"signal"` } // FastControlNode The module relay (control node) descriptor for fast network @@ -312,6 +312,7 @@ func (s *FastModularNetworkSolver) forwardStep(maxAllowedSignalDelta float64) (i func (s *FastModularNetworkSolver) Flush() (bool, error) { for i := s.biasNeuronCount; i < s.totalNeuronCount; i++ { s.neuronSignals[i] = 0.0 + s.neuronSignalsBeingProcessed[i] = 0.0 } return true, nil } diff --git a/neat/network/fast_network_model_io.go b/neat/network/fast_network_model_io.go new file mode 100644 index 0000000..1759142 --- /dev/null +++ b/neat/network/fast_network_model_io.go @@ -0,0 +1,77 @@ +package network + +import ( + "encoding/json" + "github.com/yaricom/goNEAT/v4/neat/math" + "io" +) + +// WriteModel is to write this FastModularNetworkSolver as a model to be used later. +func (s *FastModularNetworkSolver) WriteModel(w io.Writer) error { + dataHolder := newFastModularNetworkSolverData(s) + enc := json.NewEncoder(w) + return enc.Encode(dataHolder) +} + +type NodeActivator struct { + NodeActivation math.NodeActivationType +} + +type fastControlNodeData struct { + ActivationType NodeActivator `json:"activation_type"` + InputIndexes []int `json:"input_indexes"` + OutputIndexes []int `json:"output_indexes"` +} + +type fastModularNetworkSolverData struct { + Id int `json:"id"` + Name string `json:"name"` + InputNeuronCount int `json:"input_neuron_count"` + SensorNeuronCount int `json:"sensor_neuron_count"` + OutputNeuronCount int `json:"output_neuron_count"` + BiasNeuronCount int `json:"bias_neuron_count"` + TotalNeuronCount int `json:"total_neuron_count"` + ActivationFunctions []NodeActivator `json:"activation_functions"` + BiasList []float64 `json:"bias_list"` + Connections []*FastNetworkLink `json:"connections"` + Modules []fastControlNodeData `json:"modules"` +} + +func newFastModularNetworkSolverData(n *FastModularNetworkSolver) *fastModularNetworkSolverData { + data := &fastModularNetworkSolverData{ + Id: n.Id, + Name: n.Name, + InputNeuronCount: n.inputNeuronCount, + SensorNeuronCount: n.sensorNeuronCount, + OutputNeuronCount: n.outputNeuronCount, + BiasNeuronCount: n.biasNeuronCount, + TotalNeuronCount: n.totalNeuronCount, + ActivationFunctions: make([]NodeActivator, 0, len(n.activationFunctions)), + BiasList: n.biasList, + Connections: n.connections, + Modules: make([]fastControlNodeData, 0), + } + for _, v := range n.activationFunctions { + data.ActivationFunctions = append(data.ActivationFunctions, NodeActivator{ + NodeActivation: v, + }) + } + if n.modules != nil { + for _, v := range n.modules { + data.Modules = append(data.Modules, fastControlNodeData{ + ActivationType: NodeActivator{NodeActivation: v.ActivationType}, + InputIndexes: v.InputIndexes, + OutputIndexes: v.OutputIndexes, + }) + } + } + return data +} + +func (n *NodeActivator) MarshalText() ([]byte, error) { + if activationName, err := math.NodeActivators.ActivationNameFromType(n.NodeActivation); err != nil { + return nil, err + } else { + return []byte(activationName), nil + } +} diff --git a/neat/network/fast_network_model_io_test.go b/neat/network/fast_network_model_io_test.go new file mode 100644 index 0000000..d513274 --- /dev/null +++ b/neat/network/fast_network_model_io_test.go @@ -0,0 +1,56 @@ +package network + +import ( + "bytes" + "encoding/json" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +const jsonFMNStr = `{"id":0,"name":"","input_neuron_count":2,"sensor_neuron_count":3,"output_neuron_count":2,"bias_neuron_count":1,"total_neuron_count":8,"activation_functions":["SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation"],"bias_list":[0,0,0,0,0,0,1,0],"connections":[{"source_index":1,"target_index":5,"weight":15,"signal":0},{"source_index":2,"target_index":5,"weight":10,"signal":0},{"source_index":2,"target_index":6,"weight":5,"signal":0},{"source_index":6,"target_index":7,"weight":17,"signal":0},{"source_index":5,"target_index":3,"weight":7,"signal":0},{"source_index":7,"target_index":3,"weight":4.5,"signal":0},{"source_index":7,"target_index":4,"weight":13,"signal":0}],"modules":[]}` +const jsonFNMStrModule = `{"id":0,"name":"","input_neuron_count":2,"sensor_neuron_count":3,"output_neuron_count":2,"bias_neuron_count":1,"total_neuron_count":8,"activation_functions":["SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","LinearActivation","LinearActivation","LinearActivation","LinearActivation","NullActivation"],"bias_list":[0,0,0,0,0,10,1,0],"connections":[{"source_index":1,"target_index":5,"weight":15,"signal":0},{"source_index":2,"target_index":6,"weight":5,"signal":0},{"source_index":7,"target_index":3,"weight":4.5,"signal":0},{"source_index":7,"target_index":4,"weight":13,"signal":0}],"modules":[{"activation_type":"MultiplyModuleActivation","input_indexes":[5,6],"output_indexes":[7]}]}` + +func TestFastModularNetworkSolver_WriteModel_NoModule(t *testing.T) { + net := buildNetwork() + + fmm, err := net.FastNetworkSolver() + require.NoError(t, err, "failed to create fast network solver") + + outBuf := bytes.NewBufferString("") + err = fmm.(*FastModularNetworkSolver).WriteModel(outBuf) + require.NoError(t, err, "failed to write model") + + println(outBuf.String()) + + var expected interface{} + err = json.Unmarshal([]byte(jsonFMNStr), &expected) + require.NoError(t, err, "failed to unmarshal expected json") + var actual interface{} + err = json.Unmarshal(outBuf.Bytes(), &actual) + require.NoError(t, err, "failed to unmarshal actual json") + + assert.EqualValues(t, expected, actual, "model JSON does not match expected JSON") +} + +func TestFastModularNetworkSolver_WriteModel_WithModule(t *testing.T) { + net := buildModularNetwork() + + fmm, err := net.FastNetworkSolver() + require.NoError(t, err, "failed to create fast network solver") + + outBuf := bytes.NewBufferString("") + err = fmm.(*FastModularNetworkSolver).WriteModel(outBuf) + require.NoError(t, err, "failed to write model") + + println(outBuf.String()) + + var expected interface{} + err = json.Unmarshal([]byte(jsonFNMStrModule), &expected) + require.NoError(t, err, "failed to unmarshal expected json") + var actual interface{} + err = json.Unmarshal(outBuf.Bytes(), &actual) + require.NoError(t, err, "failed to unmarshal actual json") + + assert.EqualValues(t, expected, actual, "model JSON does not match expected JSON") +} From fe2316192c18fcc64138b9125eeb6da36969ad32 Mon Sep 17 00:00:00 2001 From: Iaroslav Omelianenko Date: Mon, 13 May 2024 22:00:28 +0300 Subject: [PATCH 2/8] Implemented deserialization of the `FastModularNetworkSolver`. Implemented related test cases. --- neat/network/fast_network_model_io.go | 47 ++++++++++-- neat/network/fast_network_model_io_test.go | 85 ++++++++++++++++++++++ 2 files changed, 127 insertions(+), 5 deletions(-) diff --git a/neat/network/fast_network_model_io.go b/neat/network/fast_network_model_io.go index 1759142..9eec108 100644 --- a/neat/network/fast_network_model_io.go +++ b/neat/network/fast_network_model_io.go @@ -13,6 +13,38 @@ func (s *FastModularNetworkSolver) WriteModel(w io.Writer) error { return enc.Encode(dataHolder) } +// ReadFMNSModel allows loading model encoding FastModularNetworkSolver. +func ReadFMNSModel(reader io.Reader) (*FastModularNetworkSolver, error) { + var data fastModularNetworkSolverData + dec := json.NewDecoder(reader) + if err := dec.Decode(&data); err != nil { + return nil, err + } + activationFunctions := make([]math.NodeActivationType, len(data.ActivationFunctions)) + for i, f := range data.ActivationFunctions { + activationFunctions[i] = f.NodeActivation + } + var modules []*FastControlNode + if len(data.Modules) > 0 { + modules = make([]*FastControlNode, len(data.Modules)) + for i, m := range data.Modules { + modules[i] = &FastControlNode{ + ActivationType: m.ActivationType.NodeActivation, + InputIndexes: m.InputIndexes, + OutputIndexes: m.OutputIndexes, + } + } + } + fmns := NewFastModularNetworkSolver( + data.BiasNeuronCount, data.InputNeuronCount, data.OutputNeuronCount, + data.TotalNeuronCount, activationFunctions, + data.Connections, data.BiasList, modules, + ) + fmns.Name = data.Name + fmns.Id = data.Id + return fmns, nil +} + type NodeActivator struct { NodeActivation math.NodeActivationType } @@ -34,7 +66,7 @@ type fastModularNetworkSolverData struct { ActivationFunctions []NodeActivator `json:"activation_functions"` BiasList []float64 `json:"bias_list"` Connections []*FastNetworkLink `json:"connections"` - Modules []fastControlNodeData `json:"modules"` + Modules []fastControlNodeData `json:"modules,omitempty"` } func newFastModularNetworkSolverData(n *FastModularNetworkSolver) *fastModularNetworkSolverData { @@ -46,15 +78,15 @@ func newFastModularNetworkSolverData(n *FastModularNetworkSolver) *fastModularNe OutputNeuronCount: n.outputNeuronCount, BiasNeuronCount: n.biasNeuronCount, TotalNeuronCount: n.totalNeuronCount, - ActivationFunctions: make([]NodeActivator, 0, len(n.activationFunctions)), + ActivationFunctions: make([]NodeActivator, len(n.activationFunctions)), BiasList: n.biasList, Connections: n.connections, Modules: make([]fastControlNodeData, 0), } - for _, v := range n.activationFunctions { - data.ActivationFunctions = append(data.ActivationFunctions, NodeActivator{ + for i, v := range n.activationFunctions { + data.ActivationFunctions[i] = NodeActivator{ NodeActivation: v, - }) + } } if n.modules != nil { for _, v := range n.modules { @@ -75,3 +107,8 @@ func (n *NodeActivator) MarshalText() ([]byte, error) { return []byte(activationName), nil } } + +func (n *NodeActivator) UnmarshalText(text []byte) (err error) { + n.NodeActivation, err = math.NodeActivators.ActivationTypeFromName(string(text)) + return err +} diff --git a/neat/network/fast_network_model_io_test.go b/neat/network/fast_network_model_io_test.go index d513274..8bc5eab 100644 --- a/neat/network/fast_network_model_io_test.go +++ b/neat/network/fast_network_model_io_test.go @@ -54,3 +54,88 @@ func TestFastModularNetworkSolver_WriteModel_WithModule(t *testing.T) { assert.EqualValues(t, expected, actual, "model JSON does not match expected JSON") } + +func TestReadFMNSModel_NoModule(t *testing.T) { + buf := bytes.NewBufferString(jsonFMNStr) + + fmm, err := ReadFMNSModel(buf) + assert.NoError(t, err, "failed to read model") + assert.NotNil(t, fmm, "failed to deserialize model") + + data := []float64{1.5, 2.0} // bias inherent + err = fmm.LoadSensors(data) + require.NoError(t, err, "failed to load sensors") + + // test that it operates as expected + // + net := buildNetwork() + depth, err := net.MaxActivationDepth() + require.NoError(t, err, "failed to calculate max depth") + + t.Logf("depth: %d\n", depth) + logNetworkActivationPath(net, t) + + data = append(data, 1.0) // BIAS is third object + err = net.LoadSensors(data) + require.NoError(t, err, "failed to load sensors") + res, err := net.ForwardSteps(depth) + require.NoError(t, err, "error when trying to activate objective network") + require.True(t, res, "failed to activate objective network") + + // do forward steps through the solver and test results + // + res, err = fmm.ForwardSteps(depth) + require.NoError(t, err, "error while do forward steps") + require.True(t, res, "forward steps returned false") + + // check results by comparing activations of objective network and fast network solver + // + outputs := fmm.ReadOutputs() + for i, out := range outputs { + assert.Equal(t, net.Outputs[i].Activation, out, "wrong activation at: %d", i) + } +} + +func TestReadFMNSModel_ModularNetwork(t *testing.T) { + buf := bytes.NewBufferString(jsonFNMStrModule) + + fmm, err := ReadFMNSModel(buf) + assert.NoError(t, err, "failed to read model") + assert.NotNil(t, fmm, "failed to deserialize model") + + data := []float64{1.0, 2.0} // bias inherent + err = fmm.LoadSensors(data) + require.NoError(t, err, "failed to load sensors") + + // test that it operates as expected + // + net := buildModularNetwork() + depth, err := net.MaxActivationDepth() + require.NoError(t, err, "failed to calculate max depth") + + t.Logf("depth: %d\n", depth) + logNetworkActivationPath(net, t) + + // activate objective network + // + data = append(data, 1.0) // BIAS is third object + err = net.LoadSensors(data) + require.NoError(t, err, "failed to load sensors") + res, err := net.ForwardSteps(depth) + require.NoError(t, err, "error when trying to activate objective network") + require.True(t, res, "failed to activate objective network") + + // do forward steps through the solver and test results + // + res, err = fmm.ForwardSteps(depth) + require.NoError(t, err, "error while do forward steps") + require.True(t, res, "forward steps returned false") + + // check results by comparing activations of objective network and fast network solver + // + outputs := fmm.ReadOutputs() + for i, out := range outputs { + assert.Equal(t, net.Outputs[i].Activation, out, "wrong activation at: %d", i) + } + +} From a2ebe73a6e2abde8ec5ddc2c29e0524551d6e815 Mon Sep 17 00:00:00 2001 From: Iaroslav Omelianenko Date: Tue, 14 May 2024 12:44:19 +0300 Subject: [PATCH 3/8] Fixed failing unit test --- neat/network/fast_network_model_io_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/neat/network/fast_network_model_io_test.go b/neat/network/fast_network_model_io_test.go index 8bc5eab..cf91d01 100644 --- a/neat/network/fast_network_model_io_test.go +++ b/neat/network/fast_network_model_io_test.go @@ -8,7 +8,7 @@ import ( "testing" ) -const jsonFMNStr = `{"id":0,"name":"","input_neuron_count":2,"sensor_neuron_count":3,"output_neuron_count":2,"bias_neuron_count":1,"total_neuron_count":8,"activation_functions":["SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation"],"bias_list":[0,0,0,0,0,0,1,0],"connections":[{"source_index":1,"target_index":5,"weight":15,"signal":0},{"source_index":2,"target_index":5,"weight":10,"signal":0},{"source_index":2,"target_index":6,"weight":5,"signal":0},{"source_index":6,"target_index":7,"weight":17,"signal":0},{"source_index":5,"target_index":3,"weight":7,"signal":0},{"source_index":7,"target_index":3,"weight":4.5,"signal":0},{"source_index":7,"target_index":4,"weight":13,"signal":0}],"modules":[]}` +const jsonFMNStr = `{"id":0,"name":"","input_neuron_count":2,"sensor_neuron_count":3,"output_neuron_count":2,"bias_neuron_count":1,"total_neuron_count":8,"activation_functions":["SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation"],"bias_list":[0,0,0,0,0,0,1,0],"connections":[{"source_index":1,"target_index":5,"weight":15,"signal":0},{"source_index":2,"target_index":5,"weight":10,"signal":0},{"source_index":2,"target_index":6,"weight":5,"signal":0},{"source_index":6,"target_index":7,"weight":17,"signal":0},{"source_index":5,"target_index":3,"weight":7,"signal":0},{"source_index":7,"target_index":3,"weight":4.5,"signal":0},{"source_index":7,"target_index":4,"weight":13,"signal":0}]}` const jsonFNMStrModule = `{"id":0,"name":"","input_neuron_count":2,"sensor_neuron_count":3,"output_neuron_count":2,"bias_neuron_count":1,"total_neuron_count":8,"activation_functions":["SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","LinearActivation","LinearActivation","LinearActivation","LinearActivation","NullActivation"],"bias_list":[0,0,0,0,0,10,1,0],"connections":[{"source_index":1,"target_index":5,"weight":15,"signal":0},{"source_index":2,"target_index":6,"weight":5,"signal":0},{"source_index":7,"target_index":3,"weight":4.5,"signal":0},{"source_index":7,"target_index":4,"weight":13,"signal":0}],"modules":[{"activation_type":"MultiplyModuleActivation","input_indexes":[5,6],"output_indexes":[7]}]}` func TestFastModularNetworkSolver_WriteModel_NoModule(t *testing.T) { From 030c366720b7d2a881c2cf8a56cd7df5b374c69c Mon Sep 17 00:00:00 2001 From: Iaroslav Omelianenko Date: Tue, 14 May 2024 19:22:15 +0300 Subject: [PATCH 4/8] Implemented proper network's Name and ID handling --- neat/network/fast_network.go | 6 ++-- neat/network/fast_network_model_io_test.go | 34 ++++++++++++++++++---- neat/network/network.go | 7 +++-- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/neat/network/fast_network.go b/neat/network/fast_network.go index a0017d8..4564d0b 100644 --- a/neat/network/fast_network.go +++ b/neat/network/fast_network.go @@ -133,6 +133,7 @@ func NewFastModularNetworkSolver(biasNeuronCount, inputNeuronCount, outputNeuron } // ForwardSteps Propagates activation wave through all network nodes provided number of steps in forward direction. +// See also Relax for conditional activation waves propagation. // Returns true if activation wave passed from all inputs to the outputs. func (s *FastModularNetworkSolver) ForwardSteps(steps int) (res bool, err error) { for i := 0; i < steps; i++ { @@ -233,8 +234,9 @@ func (s *FastModularNetworkSolver) recursiveActivateNode(currentNode int) (res b return res, err } -// Relax Attempts to relax network given amount of steps until giving up. The network considered relaxed when absolute -// value of the change at any given point is less than maxAllowedSignalDelta during activation waves propagation. +// Relax Attempts to relax network (propagate activation waves) given amount of steps until giving up. +// The network considered relaxed when absolute value of the change at any given point is less +// than maxAllowedSignalDelta during activation waves propagation. // If maxAllowedSignalDelta value is less than or equal to 0, the method will return true without checking for relaxation. func (s *FastModularNetworkSolver) Relax(maxSteps int, maxAllowedSignalDelta float64) (relaxed bool, err error) { for i := 0; i < maxSteps; i++ { diff --git a/neat/network/fast_network_model_io_test.go b/neat/network/fast_network_model_io_test.go index cf91d01..6e98d28 100644 --- a/neat/network/fast_network_model_io_test.go +++ b/neat/network/fast_network_model_io_test.go @@ -8,11 +8,14 @@ import ( "testing" ) -const jsonFMNStr = `{"id":0,"name":"","input_neuron_count":2,"sensor_neuron_count":3,"output_neuron_count":2,"bias_neuron_count":1,"total_neuron_count":8,"activation_functions":["SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation"],"bias_list":[0,0,0,0,0,0,1,0],"connections":[{"source_index":1,"target_index":5,"weight":15,"signal":0},{"source_index":2,"target_index":5,"weight":10,"signal":0},{"source_index":2,"target_index":6,"weight":5,"signal":0},{"source_index":6,"target_index":7,"weight":17,"signal":0},{"source_index":5,"target_index":3,"weight":7,"signal":0},{"source_index":7,"target_index":3,"weight":4.5,"signal":0},{"source_index":7,"target_index":4,"weight":13,"signal":0}]}` -const jsonFNMStrModule = `{"id":0,"name":"","input_neuron_count":2,"sensor_neuron_count":3,"output_neuron_count":2,"bias_neuron_count":1,"total_neuron_count":8,"activation_functions":["SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","LinearActivation","LinearActivation","LinearActivation","LinearActivation","NullActivation"],"bias_list":[0,0,0,0,0,10,1,0],"connections":[{"source_index":1,"target_index":5,"weight":15,"signal":0},{"source_index":2,"target_index":6,"weight":5,"signal":0},{"source_index":7,"target_index":3,"weight":4.5,"signal":0},{"source_index":7,"target_index":4,"weight":13,"signal":0}],"modules":[{"activation_type":"MultiplyModuleActivation","input_indexes":[5,6],"output_indexes":[7]}]}` +const jsonFMNStr = `{"id":123456,"name":"test network","input_neuron_count":2,"sensor_neuron_count":3,"output_neuron_count":2,"bias_neuron_count":1,"total_neuron_count":8,"activation_functions":["SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation"],"bias_list":[0,0,0,0,0,0,1,0],"connections":[{"source_index":1,"target_index":5,"weight":15,"signal":0},{"source_index":2,"target_index":5,"weight":10,"signal":0},{"source_index":2,"target_index":6,"weight":5,"signal":0},{"source_index":6,"target_index":7,"weight":17,"signal":0},{"source_index":5,"target_index":3,"weight":7,"signal":0},{"source_index":7,"target_index":3,"weight":4.5,"signal":0},{"source_index":7,"target_index":4,"weight":13,"signal":0}]}` +const jsonFNMStrModule = `{"id":123456,"name":"test network","input_neuron_count":2,"sensor_neuron_count":3,"output_neuron_count":2,"bias_neuron_count":1,"total_neuron_count":8,"activation_functions":["SigmoidSteepenedActivation","SigmoidSteepenedActivation","SigmoidSteepenedActivation","LinearActivation","LinearActivation","LinearActivation","LinearActivation","NullActivation"],"bias_list":[0,0,0,0,0,10,1,0],"connections":[{"source_index":1,"target_index":5,"weight":15,"signal":0},{"source_index":2,"target_index":6,"weight":5,"signal":0},{"source_index":7,"target_index":3,"weight":4.5,"signal":0},{"source_index":7,"target_index":4,"weight":13,"signal":0}],"modules":[{"activation_type":"MultiplyModuleActivation","input_indexes":[5,6],"output_indexes":[7]}]}` + +const networkName = "test network" +const networkId = 123456 func TestFastModularNetworkSolver_WriteModel_NoModule(t *testing.T) { - net := buildNetwork() + net := buildNamedNetwork(networkName, networkId) fmm, err := net.FastNetworkSolver() require.NoError(t, err, "failed to create fast network solver") @@ -34,7 +37,7 @@ func TestFastModularNetworkSolver_WriteModel_NoModule(t *testing.T) { } func TestFastModularNetworkSolver_WriteModel_WithModule(t *testing.T) { - net := buildModularNetwork() + net := buildNamedModularNetwork(networkName, networkId) fmm, err := net.FastNetworkSolver() require.NoError(t, err, "failed to create fast network solver") @@ -62,6 +65,9 @@ func TestReadFMNSModel_NoModule(t *testing.T) { assert.NoError(t, err, "failed to read model") assert.NotNil(t, fmm, "failed to deserialize model") + assert.Equal(t, fmm.Name, networkName, "wrong network name") + assert.Equal(t, fmm.Id, networkId, "wrong network id") + data := []float64{1.5, 2.0} // bias inherent err = fmm.LoadSensors(data) require.NoError(t, err, "failed to load sensors") @@ -84,7 +90,7 @@ func TestReadFMNSModel_NoModule(t *testing.T) { // do forward steps through the solver and test results // - res, err = fmm.ForwardSteps(depth) + res, err = fmm.Relax(depth, .1) require.NoError(t, err, "error while do forward steps") require.True(t, res, "forward steps returned false") @@ -103,6 +109,9 @@ func TestReadFMNSModel_ModularNetwork(t *testing.T) { assert.NoError(t, err, "failed to read model") assert.NotNil(t, fmm, "failed to deserialize model") + assert.Equal(t, fmm.Name, networkName, "wrong network name") + assert.Equal(t, fmm.Id, networkId, "wrong network id") + data := []float64{1.0, 2.0} // bias inherent err = fmm.LoadSensors(data) require.NoError(t, err, "failed to load sensors") @@ -127,7 +136,7 @@ func TestReadFMNSModel_ModularNetwork(t *testing.T) { // do forward steps through the solver and test results // - res, err = fmm.ForwardSteps(depth) + res, err = fmm.Relax(depth, 1) require.NoError(t, err, "error while do forward steps") require.True(t, res, "forward steps returned false") @@ -139,3 +148,16 @@ func TestReadFMNSModel_ModularNetwork(t *testing.T) { } } +func buildNamedNetwork(name string, id int) *Network { + net := buildNetwork() + net.Name = name + net.Id = id + return net +} + +func buildNamedModularNetwork(name string, id int) *Network { + net := buildModularNetwork() + net.Name = name + net.Id = id + return net +} diff --git a/neat/network/network.go b/neat/network/network.go index 9c9b0c9..8df2322 100644 --- a/neat/network/network.go +++ b/neat/network/network.go @@ -135,8 +135,11 @@ func (n *Network) FastNetworkSolver() (Solver, error) { modules[i] = &FastControlNode{InputIndexes: inputs, OutputIndexes: outputs, ActivationType: cn.ActivationType} } - return NewFastModularNetworkSolver(biasNeuronCount, inputNeuronCount, outputNeuronCount, totalNeuronCount, - activations, connections, biases, modules), nil + solver := NewFastModularNetworkSolver(biasNeuronCount, inputNeuronCount, outputNeuronCount, totalNeuronCount, + activations, connections, biases, modules) + solver.Id = n.Id + solver.Name = n.Name + return solver, nil } func processList(startIndex int, nList []*NNode, activations []math.NodeActivationType, neuronLookup map[int]int) int { From 1eaa957af1c6316f1eda682495eb95ff58629512 Mon Sep 17 00:00:00 2001 From: Iaroslav Omelianenko Date: Tue, 14 May 2024 19:24:01 +0300 Subject: [PATCH 5/8] Fixed naming to avoid collisions with built is function name. --- neat/network/network.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/neat/network/network.go b/neat/network/network.go index 8df2322..a4a8023 100644 --- a/neat/network/network.go +++ b/neat/network/network.go @@ -454,18 +454,18 @@ func (n *Network) MaxActivationDepthWithCap(maxDepthCap int) (int, error) { return 1, nil // just one layer depth } - max := 0 // The max depth + maxDepth := 0 // The max depth for _, node := range n.Outputs { currDepth, err := node.Depth(0, maxDepthCap) if err != nil { return currDepth, err } - if currDepth > max { - max = currDepth + if currDepth > maxDepth { + maxDepth = currDepth } } - return max, nil + return maxDepth, nil } // AllNodes Returns all network nodes including MIMO control nodes: base nodes + control nodes @@ -491,7 +491,7 @@ func (n *Network) maxActivationDepthModular(w io.Writer) (int, error) { // negative cycle detected - fallback to FloydWarshall allPaths, _ = path.FloydWarshall(n) } - max := 0 // The max depth + maxDepth := 0 // The max depth for _, in := range n.inputs { for _, out := range n.Outputs { if paths, _ := allPaths.AllBetween(in.ID(), out.ID()); paths != nil { @@ -503,8 +503,8 @@ func (n *Network) maxActivationDepthModular(w io.Writer) (int, error) { // iterate over returned paths and find the one with maximal length for _, p := range paths { l := len(p) - 1 // to exclude input node - if l > max { - max = l + if l > maxDepth { + maxDepth = l } } } @@ -516,5 +516,5 @@ func (n *Network) maxActivationDepthModular(w io.Writer) (int, error) { } } - return max, nil + return maxDepth, nil } From 178b312aa4bb9e2cbaeae2ac99bc2f9c2436af0a Mon Sep 17 00:00:00 2001 From: Iaroslav Omelianenko Date: Tue, 14 May 2024 19:25:49 +0300 Subject: [PATCH 6/8] Fixed switch statement --- neat/network/network.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/neat/network/network.go b/neat/network/network.go index a4a8023..00dce6d 100644 --- a/neat/network/network.go +++ b/neat/network/network.go @@ -73,6 +73,8 @@ func (n *Network) FastNetworkSolver() (Solver, error) { inList = append(inList, ne) case HiddenNeuron: hiddenList = append(hiddenList, ne) + default: + // skip } } inputNeuronCount := len(inList) From 52c6a2702e91f30905809ff226e656404de16e6d Mon Sep 17 00:00:00 2001 From: Iaroslav Omelianenko Date: Tue, 14 May 2024 19:32:30 +0300 Subject: [PATCH 7/8] Fixed docstrings --- neat/network/fast_network.go | 16 ---------------- neat/network/solver.go | 6 ++++-- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/neat/network/fast_network.go b/neat/network/fast_network.go index 4564d0b..9413c3b 100644 --- a/neat/network/fast_network.go +++ b/neat/network/fast_network.go @@ -132,9 +132,6 @@ func NewFastModularNetworkSolver(biasNeuronCount, inputNeuronCount, outputNeuron return &fmm } -// ForwardSteps Propagates activation wave through all network nodes provided number of steps in forward direction. -// See also Relax for conditional activation waves propagation. -// Returns true if activation wave passed from all inputs to the outputs. func (s *FastModularNetworkSolver) ForwardSteps(steps int) (res bool, err error) { for i := 0; i < steps; i++ { if res, err = s.forwardStep(0); err != nil { @@ -144,9 +141,6 @@ func (s *FastModularNetworkSolver) ForwardSteps(steps int) (res bool, err error) return res, nil } -// RecursiveSteps Propagates activation wave through all network nodes provided number of steps by recursion from output nodes -// Returns true if activation wave passed from all inputs to the outputs. This method is preferred method -// of network activation when number of forward steps can not be easy calculated and no network modules are set. func (s *FastModularNetworkSolver) RecursiveSteps() (res bool, err error) { if len(s.modules) > 0 { return false, errors.New("recursive activation can not be used for network with defined modules") @@ -234,10 +228,6 @@ func (s *FastModularNetworkSolver) recursiveActivateNode(currentNode int) (res b return res, err } -// Relax Attempts to relax network (propagate activation waves) given amount of steps until giving up. -// The network considered relaxed when absolute value of the change at any given point is less -// than maxAllowedSignalDelta during activation waves propagation. -// If maxAllowedSignalDelta value is less than or equal to 0, the method will return true without checking for relaxation. func (s *FastModularNetworkSolver) Relax(maxSteps int, maxAllowedSignalDelta float64) (relaxed bool, err error) { for i := 0; i < maxSteps; i++ { if relaxed, err = s.forwardStep(maxAllowedSignalDelta); err != nil { @@ -309,8 +299,6 @@ func (s *FastModularNetworkSolver) forwardStep(maxAllowedSignalDelta float64) (i return isRelaxed, err } -// Flush Flushes network state by removing all current activations. Returns true if network flushed successfully or -// false in case of error. func (s *FastModularNetworkSolver) Flush() (bool, error) { for i := s.biasNeuronCount; i < s.totalNeuronCount; i++ { s.neuronSignals[i] = 0.0 @@ -319,7 +307,6 @@ func (s *FastModularNetworkSolver) Flush() (bool, error) { return true, nil } -// LoadSensors Set sensors values to the input nodes of the network func (s *FastModularNetworkSolver) LoadSensors(inputs []float64) error { if len(inputs) == s.inputNeuronCount { // only inputs should be provided @@ -332,7 +319,6 @@ func (s *FastModularNetworkSolver) LoadSensors(inputs []float64) error { return nil } -// ReadOutputs Read output values from the output nodes of the network func (s *FastModularNetworkSolver) ReadOutputs() []float64 { // decouple and return outs := make([]float64, s.outputNeuronCount) @@ -340,12 +326,10 @@ func (s *FastModularNetworkSolver) ReadOutputs() []float64 { return outs } -// NodeCount Returns the total number of neural units in the network func (s *FastModularNetworkSolver) NodeCount() int { return s.totalNeuronCount + len(s.modules) } -// LinkCount Returns the total number of links between nodes in the network func (s *FastModularNetworkSolver) LinkCount() int { // count all connections numLinks := len(s.connections) diff --git a/neat/network/solver.go b/neat/network/solver.go index 132542d..4d1b05b 100644 --- a/neat/network/solver.go +++ b/neat/network/solver.go @@ -4,6 +4,7 @@ package network type Solver interface { // ForwardSteps Propagates activation wave through all network nodes provided number of steps in forward direction. // Normally the number of steps should be equal to the activation depth of the network. + // See also Relax for conditional activation waves propagation. // Returns true if activation wave passed from all inputs to the output nodes. ForwardSteps(steps int) (bool, error) @@ -11,8 +12,9 @@ type Solver interface { // Returns true if activation wave passed from all inputs to the output nodes. RecursiveSteps() (bool, error) - // Relax Attempts to relax network given amount of steps until giving up. The network considered relaxed when absolute - // value of the change at any given point is less than maxAllowedSignalDelta during activation waves propagation. + // Relax Attempts to relax network (propagate activation waves) given amount of steps until giving up. + // The network considered relaxed when absolute value of the change at any given point is less + // than maxAllowedSignalDelta during activation waves propagation. // If maxAllowedSignalDelta value is less than or equal to 0, the method will return true without checking for relaxation. Relax(maxSteps int, maxAllowedSignalDelta float64) (bool, error) From 692fab444fbe0a4eaa69c5f28576c95f87bce845 Mon Sep 17 00:00:00 2001 From: Iaroslav Omelianenko Date: Tue, 14 May 2024 19:52:25 +0300 Subject: [PATCH 8/8] Updated workflow to use `Codecov` action --- .github/workflows/ci.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d2ea62..9b312db 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,10 +23,15 @@ jobs: env: GO111MODULE: on - - name: Run coverage + - name: Run Tests run: go test -coverprofile=coverage.txt -covermode=atomic -timeout 40m -v ./... env: 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