func (g *Genome) mutateNodeTrait(times int) (bool, error) {
	if len(g.Traits) == 0 || len(g.Nodes) == 0 {
		return false, errors.New("genome has either no traits or nodes")
	}
	for loop := 0; loop < times; loop++ {
		traitNum := rand.Intn(len(g.Traits))
		nodeNum := rand.Intn(len(g.Nodes))
		g.Nodes[nodeNum].Trait = g.Traits[traitNum]
	}
	return true, nil
}
This chooses a random node and re-points the node to a random trait specified number of times
package genetics
import (
	"errors"
	"fmt"
	"github.com/yaricom/goNEAT/v2/neat"
	"github.com/yaricom/goNEAT/v2/neat/math"
	"github.com/yaricom/goNEAT/v2/neat/network"
	"math/rand"
)
func (g *Genome) mutateConnectSensors(innovations InnovationsObserver, _ *neat.Options) (bool, error) {
	if len(g.Genes) == 0 {
		return false, errors.New("genome has no genes")
	}
	sensors := make([]*network.NNode, 0)
	outputs := make([]*network.NNode, 0)
	for _, n := range g.Nodes {
		if n.IsSensor() {
			sensors = append(sensors, n)
		} else {
			outputs = append(outputs, n)
		}
	}
	disconnectedSensors := make([]*network.NNode, 0)
	for _, sensor := range sensors {
		connected := false
		for _, gene := range g.Genes {
			if gene.Link.InNode.Id == sensor.Id {
				connected = true
				break
			}
		}
		if !connected {
			disconnectedSensors = append(disconnectedSensors, sensor)
		}
	}
	if len(disconnectedSensors) == 0 {
		return false, nil
	}
	sensor := disconnectedSensors[rand.Intn(len(disconnectedSensors))]
	linkAdded := false
	for _, output := range outputs {
		found := false
		for _, gene := range g.Genes {
			if gene.Link.InNode == sensor && gene.Link.OutNode == output {
				found = true
				break
			}
		}
		if !found {
			var gene *Gene
			innovationFound := false
			for _, inn := range innovations.Innovations() {
				if inn.innovationType == newLinkInnType &&
					inn.InNodeId == sensor.Id &&
					inn.OutNodeId == output.Id &&
					!inn.IsRecurrent {
					gene = NewGeneWithTrait(g.Traits[inn.NewTraitNum], inn.NewWeight,
						sensor, output, false, inn.InnovationNum, 0)
					innovationFound = true
					break
				}
			}
			if !innovationFound {
				traitNum := rand.Intn(len(g.Traits))
				newWeight := float64(math.RandSign()) * rand.Float64() * 10.0
				nextInnovId := innovations.NextInnovationNumber()
				gene = NewGeneWithTrait(g.Traits[traitNum], newWeight, sensor, output,
					false, nextInnovId, newWeight)
				newInnovation := NewInnovationForLink(sensor.Id, output.Id, nextInnovId,
					newWeight, traitNum)
				innovations.StoreInnovation(*newInnovation)
			} else if gene != nil && g.hasGene(gene) {
				neat.InfoLog(
					fmt.Sprintf("GENOME: Connect sensors innovation found [%t] in the same genome [%d] for gene: %s\n%s",
						innovationFound, g.Id, gene, g))
				return false, nil
			}
			if gene != nil {
				g.Genes = geneInsert(g.Genes, gene)
				linkAdded = true
			}
		}
	}
	return linkAdded, nil
}
func (g *Genome) mutateAddLink(innovations InnovationsObserver, opts *neat.Options) (bool, error) {
	if g.Phenotype == nil {
		return false, errors.New("attempt to add link to genome with no phenotype")
	} else if len(g.Nodes) == 0 {
		return false, errors.New("genome has no nodes to be connected by new link")
	}
	nodesLen := len(g.Nodes)
	doRecur := false
	if rand.Float64() < opts.RecurOnlyProb {
		doRecur = true
	}
	firstNonSensor := 0
	for _, n := range g.Nodes {
		if n.IsSensor() {
			firstNonSensor++
		} else {
			break
		}
	}
	tryCount := 0
	var node1, node2 *network.NNode
	found := false
	for tryCount < opts.NewLinkTries {
		nodeNum1, nodeNum2 := 0, 0
		if doRecur {
			loopRecur := false
			if rand.Float64() > 0.5 {
				loopRecur = true
			}
			if loopRecur {
				nodeNum1 = firstNonSensor + rand.Intn(nodesLen-firstNonSensor)  
				nodeNum2 = nodeNum1
			} else {
				for nodeNum1 == nodeNum2 {
					nodeNum1 = rand.Intn(nodesLen)
					nodeNum2 = firstNonSensor + rand.Intn(nodesLen-firstNonSensor)  
				}
			}
		} else {
			for nodeNum1 == nodeNum2 {
				nodeNum1 = rand.Intn(nodesLen)
				nodeNum2 = firstNonSensor + rand.Intn(nodesLen-firstNonSensor)  
			}
		}
		node1 = g.Nodes[nodeNum1]
		node2 = g.Nodes[nodeNum2]
		linkExists := false
		if node2.IsSensor() {
			linkExists = true
		} else {
			for _, gene := range g.Genes {
				if gene.Link.InNode.Id == node1.Id &&
					gene.Link.OutNode.Id == node2.Id &&
					gene.Link.IsRecurrent == doRecur {
					linkExists = true
					break
				}
			}
		}
		if !linkExists {
			thresh := nodesLen * nodesLen
			count := 0
			recurFlag := g.Phenotype.IsRecurrent(node1.PhenotypeAnalogue, node2.PhenotypeAnalogue, &count, thresh)
			if count > thresh {
				if neat.LogLevel == neat.LogLevelDebug {
					neat.DebugLog(
						fmt.Sprintf("GENOME: LOOP DETECTED DURING A RECURRENCY CHECK -> "+
							"node in: %s <-> node out: %s", node1.PhenotypeAnalogue, node2.PhenotypeAnalogue))
				}
			}
			if (!recurFlag && doRecur) || (recurFlag && !doRecur) {
				tryCount++
			} else {
				tryCount = opts.NewLinkTries
				found = true
			}
		} else {
			tryCount++
		}
	}
	if node1 != nil && node2 != nil && found {
		var gene *Gene
		innovationFound := false
		for _, inn := range innovations.Innovations() {
			if inn.innovationType == newLinkInnType &&
				inn.InNodeId == node1.Id &&
				inn.OutNodeId == node2.Id &&
				inn.IsRecurrent == doRecur {
				gene = NewGeneWithTrait(g.Traits[inn.NewTraitNum], inn.NewWeight, node1, node2, doRecur, inn.InnovationNum, 0)
				innovationFound = true
				break
			}
		}
		if !innovationFound {
			traitNum := rand.Intn(len(g.Traits))
			newWeight := float64(math.RandSign()) * rand.Float64() * 10.0
			nextInnovId := innovations.NextInnovationNumber()
			gene = NewGeneWithTrait(g.Traits[traitNum], newWeight, node1, node2,
				doRecur, nextInnovId, newWeight)
			innovation := NewInnovationForRecurrentLink(node1.Id, node2.Id, nextInnovId,
				newWeight, traitNum, doRecur)
			innovations.StoreInnovation(*innovation)
		} else if gene != nil && g.hasGene(gene) {
			neat.InfoLog(
				fmt.Sprintf("GENOME: Mutate add link innovation found [%t] in the same genome [%d] for gene: %s\n%s",
					innovationFound, g.Id, gene, g))
			return false, nil
		}
		if gene != nil && gene.Link.InNode.Id == gene.Link.OutNode.Id && !doRecur {
			neat.WarnLog(fmt.Sprintf("Recurent link created when recurency is not enabled: %s", gene))
			return false, fmt.Errorf("wrong gene created\n%s", g)
		}
		if gene != nil {
			g.Genes = geneInsert(g.Genes, gene)
		}
	}
	return found, nil
}
func (g *Genome) mutateAddNode(innovations InnovationsObserver, nodeIdGenerator network.NodeIdGenerator, opts *neat.Options) (bool, error) {
	if len(g.Genes) == 0 {
		return false, nil  
	}
	found := false
	var gene *Gene
	if len(g.Genes) < 15 {
		for _, gn := range g.Genes {
			if gn.IsEnabled && gn.Link.InNode.NeuronType != network.BiasNeuron && rand.Float32() >= 0.3 {
				gene = gn
				found = true
				break
			}
		}
	} else {
		tryCount := 0
		for tryCount < 20 && !found {
			geneNum := rand.Intn(len(g.Genes))
			gene = g.Genes[geneNum]
			if gene.IsEnabled && gene.Link.InNode.NeuronType != network.BiasNeuron {
				found = true
			}
			tryCount++
		}
	}
	if !found || gene == nil {
		return false, nil
	}
	gene.IsEnabled = false
	link := gene.Link
	oldWeight := link.ConnectionWeight
	trait := link.Trait
	inNode, outNode := link.InNode, link.OutNode
	if inNode == nil || outNode == nil {
		return false, fmt.Errorf("mutateAddNode: Anomalous link found with either IN or OUT node not set. %s", link)
	}
	var gene1, gene2 *Gene
	var node *network.NNode
	innovationFound := false
	for _, inn := range innovations.Innovations() {
		if inn.innovationType == newNodeInnType &&
			inn.InNodeId == inNode.Id &&
			inn.OutNodeId == outNode.Id &&
			inn.OldInnovNum == gene.InnovationNum {
			node = network.NewNNode(inn.NewNodeId, network.HiddenNeuron)
			node.Trait = g.Traits[0]
			gene1 = NewGeneWithTrait(trait, 1.0, inNode, node, link.IsRecurrent, inn.InnovationNum, 0)
			gene2 = NewGeneWithTrait(trait, oldWeight, node, outNode, false, inn.InnovationNum2, 0)
			innovationFound = true
			break
		}
	}
	if !innovationFound {
		newNodeId := nodeIdGenerator.NextNodeId()
		node = network.NewNNode(newNodeId, network.HiddenNeuron)
		node.Trait = g.Traits[0]
		if activationType, err := opts.RandomNodeActivationType(); err != nil {
			return false, err
		} else {
			node.ActivationType = activationType
		}
		gene1Innovation := innovations.NextInnovationNumber()
		gene1 = NewGeneWithTrait(trait, 1.0, inNode, node, link.IsRecurrent, gene1Innovation, 0)
		gene2Innovation := innovations.NextInnovationNumber()
		gene2 = NewGeneWithTrait(trait, oldWeight, node, outNode, false, gene2Innovation, 0)
		innovation := NewInnovationForNode(inNode.Id, outNode.Id, gene1Innovation, gene2Innovation, node.Id, gene.InnovationNum)
		innovations.StoreInnovation(*innovation)
	} else if node != nil && g.hasNode(node) {
		neat.InfoLog(
			fmt.Sprintf("GENOME: Add node innovation found [%t] in the same genome [%d] for node [%d]\n%s",
				innovationFound, g.Id, node.Id, g))
		return false, nil
	}
	if node != nil && gene1 != nil && gene2 != nil {
		g.Genes = geneInsert(g.Genes, gene1)
		g.Genes = geneInsert(g.Genes, gene2)
		g.Nodes = nodeInsert(g.Nodes, node)
		return true, nil
	}
	return false, nil
}
func (g *Genome) mutateLinkWeights(power, rate float64, mutationType mutatorType) (bool, error) {
	if len(g.Genes) == 0 {
		return false, errors.New("genome has no genes")
	}
	severe := false
	if rand.Float64() > 0.5 {
		severe = true
	}
	num, genesCount := 0.0, float64(len(g.Genes))
	endPart := genesCount * 0.8
	var gaussPoint, coldGaussPoint float64
	for _, gene := range g.Genes {
		if severe {
			gaussPoint = 0.3
			coldGaussPoint = 0.1
		} else if genesCount >= 10.0 && num > endPart {
			gaussPoint = 0.5      
			coldGaussPoint = 0.3  
		} else {
			if rand.Float64() > 0.5 {
				gaussPoint = 1.0 - rate
				coldGaussPoint = gaussPoint - 0.1
			} else {
				gaussPoint = 1.0 - rate
				coldGaussPoint = gaussPoint  
			}
		}
		random := float64(math.RandSign()) * rand.Float64() * power
		if mutationType == gaussianMutator {
			randChoice := rand.Float64()
			if randChoice > gaussPoint {
				gene.Link.ConnectionWeight += random
			} else if randChoice > coldGaussPoint {
				gene.Link.ConnectionWeight = random
			}
		} else if mutationType == goldGaussianMutator {
			gene.Link.ConnectionWeight = random
		}
		gene.MutationNum = gene.Link.ConnectionWeight
		num += 1.0
	}
	return true, nil
}
func (g *Genome) mutateRandomTrait(context *neat.Options) (bool, error) {
	if len(g.Traits) == 0 {
		return false, errors.New("genome has no traits")
	}
	traitNum := rand.Intn(len(g.Traits))
	g.Traits[traitNum].Mutate(context.TraitMutationPower, context.TraitParamMutProb)
	return true, nil
}
func (g *Genome) mutateLinkTrait(times int) (bool, error) {
	if len(g.Traits) == 0 || len(g.Genes) == 0 {
		return false, errors.New("genome has either no traits od genes")
	}
	for loop := 0; loop < times; loop++ {
		traitNum := rand.Intn(len(g.Traits))
		geneNum := rand.Intn(len(g.Genes))
		g.Genes[geneNum].Link.Trait = g.Traits[traitNum]
	}
	return true, nil
}