// Adds a new node to the genome // // In the add node mutation, an existing connection is split and the new node placed where the old // connection used to be. The old connection is disabled and two new connections are added to the // genome. The connection between the first node in the chain and the new node is given a weight // of one, and the connection between the new node and the last node in the chain is given the // same weight as the connection being split. Splitting the connection in this way introduces a // nonlinearity (i.e. sigmoid function) where there was none before. Because the new node is // immediately integrated into the network, its effect on fitness can be evaluated right away. // Preexisting network structure is not destroyed and performs the same function, while the new // structure provides an opportunity to elaborate on the original behaviors. (Stanley, 35) func (m *Complexify) addNode(rng *rand.Rand, g *neat.Genome) { // Pick a connection to split var inno int var c0 neat.Connection found := false for k, conn := range g.Conns { c0 = conn inno = k // Ensure resultant node doesn't already exist found = true src := g.Nodes[c0.Source] tgt := g.Nodes[c0.Target] x := (src.X + tgt.X) / 2.0 y := (src.Y + tgt.Y) / 2.0 for _, node := range g.Nodes { if node.X == x && node.Y == y { found = false break } } if found { break } } if !found { return } c0.Enabled = false g.Conns[inno] = c0 // Add the new node src := g.Nodes[c0.Source] tgt := g.Nodes[c0.Target] n0 := neat.Node{NeuronType: neat.Hidden, ActivationType: m.HiddenActivation(), X: (src.X + tgt.X) / 2.0, Y: (src.Y + tgt.Y) / 2.0} n0.Innovation = m.ctx.Innovation(neat.NodeInnovation, n0.Key()) g.Nodes[n0.Innovation] = n0 // Add the new connections c1 := neat.Connection{Source: src.Innovation, Target: n0.Innovation, Enabled: true, Weight: 1.0} c1.Innovation = m.ctx.Innovation(neat.ConnInnovation, c1.Key()) g.Conns[c1.Innovation] = c1 c2 := neat.Connection{Source: n0.Innovation, Target: tgt.Innovation, Enabled: true, Weight: c0.Weight} c2.Innovation = m.ctx.Innovation(neat.ConnInnovation, c2.Key()) g.Conns[c2.Innovation] = c2 }
// Returns a genome build from the parameters func createSeed(ctx neat.Context, cfg ClassicSettings) (adam neat.Genome) { // Create the genome inputs := cfg.NumInputs() outputs := cfg.NumOutputs() adam = neat.Genome{ Nodes: make(map[int]neat.Node, 1+inputs+outputs), } nodes := make([]neat.Node, len(adam.Nodes)) node := neat.Node{NeuronType: neat.Bias, ActivationType: neat.Direct, X: 0, Y: 0} node.Innovation = ctx.Innovation(neat.NodeInnovation, node.Key()) adam.Nodes[node.Innovation] = node nodes = append(nodes, node) for i := 0; i < inputs; i++ { node = neat.Node{NeuronType: neat.Input, ActivationType: neat.Direct, X: float64(i+1) / float64(inputs), Y: 0} node.Innovation = ctx.Innovation(neat.NodeInnovation, node.Key()) adam.Nodes[node.Innovation] = node nodes = append(nodes, node) } x := 0.5 for i := 0; i < outputs; i++ { if outputs > 1 { x = float64(i) / float64(outputs-1) } node = neat.Node{NeuronType: neat.Output, ActivationType: cfg.OutputActivation(), X: x, Y: 1} node.Innovation = ctx.Innovation(neat.NodeInnovation, node.Key()) adam.Nodes[node.Innovation] = node nodes = append(nodes, node) } rng := rand.New(rand.NewSource(rand.Int63())) // adam.Conns = make(map[int]neat.Connection, (1+inputs)*outputs) // for i := 0; i < 1+inputs; i++ { // for j := 0; j < outputs; j++ { // w := (rng.Float64()*2.0 - 1.0) * cfg.WeightRange() // conn := neat.Connection{Source: nodes[i].Innovation, Target: nodes[j+1+inputs].Innovation, Enabled: true, Weight: w} // conn.Innovation = ctx.Innovation(neat.ConnInnovation, conn.Key()) // adam.Conns[conn.Innovation] = conn // } // } ts := cfg.Traits() adam.Traits = make([]float64, len(ts)) for i, trait := range ts { adam.Traits[i] = rng.Float64()*(trait.Max-trait.Min) + trait.Min // TODO: Get setting values from configuration } return adam }