func (s *FastModularNetworkSolver) ReadOutputs() []float64 {
	return s.neuronSignals[s.sensorNeuronCount : s.sensorNeuronCount+s.outputNeuronCount]
}
ReadOutputs Read output values from the output nodes of the network
package network
import (
	"errors"
	"fmt"
	neatmath "github.com/yaricom/goNEAT/v2/neat/math"
	"math"
)
type FastNetworkLink struct {
	SourceIndex int
	TargetIndex int
	Weight float64
	Signal float64
}
type FastControlNode struct {
	ActivationType neatmath.NodeActivationType
	InputIndexes []int
	OutputIndexes []int
}
type FastModularNetworkSolver struct {
	Id int
	Name string
	neuronSignals []float64
	neuronSignalsBeingProcessed []float64
	activationFunctions []neatmath.NodeActivationType
	biasList []float64
	modules []*FastControlNode
	connections []*FastNetworkLink
	inputNeuronCount int
	sensorNeuronCount int
	outputNeuronCount int
	biasNeuronCount int
	totalNeuronCount int
	activated []bool
	inActivation []bool
	lastActivation []float64
	adjacentList [][]int
	reverseAdjacentList [][]int
	adjacentMatrix [][]float64
}
func NewFastModularNetworkSolver(biasNeuronCount, inputNeuronCount, outputNeuronCount, totalNeuronCount int,
	activationFunctions []neatmath.NodeActivationType, connections []*FastNetworkLink,
	biasList []float64, modules []*FastControlNode) *FastModularNetworkSolver {
	fmm := FastModularNetworkSolver{
		biasNeuronCount:     biasNeuronCount,
		inputNeuronCount:    inputNeuronCount,
		sensorNeuronCount:   biasNeuronCount + inputNeuronCount,
		outputNeuronCount:   outputNeuronCount,
		totalNeuronCount:    totalNeuronCount,
		activationFunctions: activationFunctions,
		biasList:            biasList,
		modules:             modules,
		connections:         connections,
	}
	fmm.neuronSignals = make([]float64, totalNeuronCount)
	fmm.neuronSignalsBeingProcessed = make([]float64, totalNeuronCount)
	for i := 0; i < biasNeuronCount; i++ {
		fmm.neuronSignals[i] = 1.0  
	}
	fmm.activated = make([]bool, totalNeuronCount)
	fmm.inActivation = make([]bool, totalNeuronCount)
	fmm.lastActivation = make([]float64, totalNeuronCount)
	fmm.adjacentList = make([][]int, totalNeuronCount)
	fmm.reverseAdjacentList = make([][]int, totalNeuronCount)
	fmm.adjacentMatrix = make([][]float64, totalNeuronCount)
	for i := 0; i < totalNeuronCount; i++ {
		fmm.adjacentList[i] = make([]int, 0)
		fmm.reverseAdjacentList[i] = make([]int, 0)
		fmm.adjacentMatrix[i] = make([]float64, totalNeuronCount)
	}
	for i := 0; i < len(connections); i++ {
		crs := connections[i].SourceIndex
		crt := connections[i].TargetIndex
		fmm.adjacentList[crs] = append(fmm.adjacentList[crs], crt)
		fmm.reverseAdjacentList[crt] = append(fmm.reverseAdjacentList[crt], crs)
		fmm.adjacentMatrix[crs][crt] = connections[i].Weight
	}
	return &fmm
}
func (s *FastModularNetworkSolver) ForwardSteps(steps int) (res bool, err error) {
	for i := 0; i < steps; i++ {
		if res, err = s.forwardStep(0); err != nil {
			return false, err
		}
	}
	return res, nil
}
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")
	}
	for i := 0; i < s.totalNeuronCount; i++ {
		s.activated[i] = i < s.sensorNeuronCount
		s.inActivation[i] = false
		if i >= s.sensorNeuronCount {
			s.lastActivation[i] = s.neuronSignals[i]
		}
	}
	for i := 0; i < s.outputNeuronCount; i++ {
		index := s.sensorNeuronCount + i
		if res, err = s.recursiveActivateNode(index); err != nil {
			return false, err
		} else if !res {
			return false, fmt.Errorf("failed to recursively activate the output neuron at %d", index)
		}
	}
	return res, nil
}
func (s *FastModularNetworkSolver) recursiveActivateNode(currentNode int) (res bool, err error) {
	if s.activated[currentNode] {
		s.inActivation[currentNode] = false
		return true, nil
	}
	s.inActivation[currentNode] = true
	s.neuronSignalsBeingProcessed[currentNode] = 0
	for i := 0; i < len(s.reverseAdjacentList[currentNode]); i++ {
		currentAdjNode := s.reverseAdjacentList[currentNode][i]
		if s.inActivation[currentAdjNode] {
			s.neuronSignalsBeingProcessed[currentNode] += s.lastActivation[currentAdjNode] * s.adjacentMatrix[currentAdjNode][currentNode]
		} else {
			if !s.activated[currentAdjNode] {
				res, err = s.recursiveActivateNode(currentAdjNode)
				if err != nil {
					return false, err
				} else if !res {
					return false, fmt.Errorf("failed to recursively activate neuron at %d", currentAdjNode)
				}
			}
			s.neuronSignalsBeingProcessed[currentNode] += s.neuronSignals[currentAdjNode] * s.adjacentMatrix[currentAdjNode][currentNode]
		}
	}
	s.activated[currentNode] = true
	s.inActivation[currentNode] = false
	if s.neuronSignals[currentNode], err = neatmath.NodeActivators.ActivateByType(
		s.neuronSignalsBeingProcessed[currentNode], nil,
		s.activationFunctions[currentNode]); err != nil {
		res = false
	} else {
		res = true
	}
	return res, err
}
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 {
			return false, err
		} else if relaxed {
			break  
		}
	}
	return relaxed, nil
}
func (s *FastModularNetworkSolver) forwardStep(maxAllowedSignalDelta float64) (isRelaxed bool, err error) {
	isRelaxed = true
	for _, conn := range s.connections {
		s.neuronSignalsBeingProcessed[conn.TargetIndex] += s.neuronSignals[conn.SourceIndex] * conn.Weight
	}
	for i := s.sensorNeuronCount; i < s.totalNeuronCount; i++ {
		signal := s.neuronSignalsBeingProcessed[i]
		if s.biasNeuronCount > 0 {
			signal += s.biasList[i]
		}
		if s.neuronSignalsBeingProcessed[i], err = neatmath.NodeActivators.ActivateByType(
			signal, nil, s.activationFunctions[i]); err != nil {
			return false, err
		}
	}
	for _, module := range s.modules {
		inputs := make([]float64, len(module.InputIndexes))
		for i, inIndex := range module.InputIndexes {
			inputs[i] = s.neuronSignalsBeingProcessed[inIndex]
		}
		if outputs, err := neatmath.NodeActivators.ActivateModuleByType(inputs, nil, module.ActivationType); err == nil {
			for i, outIndex := range module.OutputIndexes {
				s.neuronSignalsBeingProcessed[outIndex] = outputs[i]
			}
		} else {
			return false, err
		}
	}
	if maxAllowedSignalDelta <= 0 {
		for i := s.sensorNeuronCount; i < s.totalNeuronCount; i++ {
			s.neuronSignals[i] = s.neuronSignalsBeingProcessed[i]
			s.neuronSignalsBeingProcessed[i] = 0
		}
	} else {
		for i := s.sensorNeuronCount; i < s.totalNeuronCount; i++ {
			isRelaxed = isRelaxed && !(math.Abs(s.neuronSignals[i]-s.neuronSignalsBeingProcessed[i]) > maxAllowedSignalDelta)
			s.neuronSignals[i] = s.neuronSignalsBeingProcessed[i]
			s.neuronSignalsBeingProcessed[i] = 0
		}
	}
	return isRelaxed, err
}
func (s *FastModularNetworkSolver) Flush() (bool, error) {
	for i := s.biasNeuronCount; i < s.totalNeuronCount; i++ {
		s.neuronSignals[i] = 0.0
	}
	return true, nil
}
func (s *FastModularNetworkSolver) LoadSensors(inputs []float64) error {
	if len(inputs) == s.inputNeuronCount {
		for i := 0; i < s.inputNeuronCount; i++ {
			s.neuronSignals[s.biasNeuronCount+i] = inputs[i]
		}
	} else {
		return NetErrUnsupportedSensorsArraySize
	}
	return nil
}