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;
	}

}