NeatChromosomeAddNodeMutationHandler.java
package net.bmahe.genetics4j.neat.mutation.chromosome;
import java.util.ArrayList;
import java.util.List;
import java.util.random.RandomGenerator;
import org.apache.commons.lang3.Validate;
import net.bmahe.genetics4j.neat.Connection;
import net.bmahe.genetics4j.neat.InnovationManager;
import net.bmahe.genetics4j.neat.NodeIdManagerRegistry;
import net.bmahe.genetics4j.neat.chromosomes.NeatChromosome;
import net.bmahe.genetics4j.neat.spec.mutation.AddNode;
public class NeatChromosomeAddNodeMutationHandler extends AbstractNeatChromosomeConnectionMutationHandler<AddNode> {
private final RandomGenerator randomGenerator;
private final InnovationManager innovationManager;
private final NodeIdManagerRegistry nodeIdManagerRegistry;
public NeatChromosomeAddNodeMutationHandler(final RandomGenerator randomGenerator,
final InnovationManager innovationManager) {
this(randomGenerator, innovationManager, new NodeIdManagerRegistry());
}
public NeatChromosomeAddNodeMutationHandler(final RandomGenerator _randomGenerator,
final InnovationManager _innovationManager,
final NodeIdManagerRegistry _nodeIdManagerRegistry) {
super(AddNode.class, _randomGenerator);
Validate.notNull(_randomGenerator);
Validate.notNull(_innovationManager);
Validate.notNull(_nodeIdManagerRegistry);
this.randomGenerator = _randomGenerator;
this.innovationManager = _innovationManager;
this.nodeIdManagerRegistry = _nodeIdManagerRegistry;
}
@Override
protected List<Connection> mutateConnection(final AddNode mutationPolicy, final NeatChromosome neatChromosome,
final Connection oldConnection, final int i) {
/*
* Add-node mutations split enabled connections only. A disabled connection may already have been split, so
* splitting it again could recreate child links that use the same stable hidden-node ID.
*/
if (oldConnection.isEnabled() == false) {
return List.of(Connection.copyOf(oldConnection));
}
final List<Connection> connections = new ArrayList<>();
final var disabledConnection = Connection.builder().from(oldConnection).isEnabled(false).build();
final var nodeIdManager = nodeIdManagerRegistry.managerFor(neatChromosome.getNodeLayout());
nodeIdManager.registerExistingNodeIds(
neatChromosome.getConnections()
.stream()
.flatMap(
connection -> java.util.stream.Stream.of(connection.fromNodeIndex(), connection.toNodeIndex()))
.toList());
final int newNodeValue = nodeIdManager.nodeIdForSplit(oldConnection.innovation());
connections.add(disabledConnection);
final boolean firstConnectionAlreadyExists = neatChromosome.getConnections()
.stream()
.anyMatch(
connection -> connection.fromNodeIndex() == oldConnection.fromNodeIndex()
&& connection.toNodeIndex() == newNodeValue);
if (firstConnectionAlreadyExists == false) {
final int firstInnovation = innovationManager.computeNewId(oldConnection.fromNodeIndex(), newNodeValue);
final var firstConnection = Connection.builder()
.from(oldConnection)
.weight(1.0f)
.toNodeIndex(newNodeValue)
.innovation(firstInnovation)
.build();
connections.add(firstConnection);
}
final boolean secondConnectionAlreadyExists = neatChromosome.getConnections()
.stream()
.anyMatch(
connection -> connection.fromNodeIndex() == newNodeValue
&& connection.toNodeIndex() == oldConnection.toNodeIndex());
if (secondConnectionAlreadyExists == false) {
final int secondInnovation = innovationManager.computeNewId(newNodeValue, oldConnection.toNodeIndex());
final var secondConnection = Connection.builder()
.from(oldConnection)
.fromNodeIndex(newNodeValue)
.innovation(secondInnovation)
.build();
connections.add(secondConnection);
}
return connections;
}
}