NeatConnectedChromosomeFactory.java

package net.bmahe.genetics4j.neat.chromosomes.factory;

import java.util.ArrayList;
import java.util.List;
import java.util.random.RandomGenerator;

import org.apache.commons.lang3.Validate;

import net.bmahe.genetics4j.core.chromosomes.factory.ChromosomeFactory;
import net.bmahe.genetics4j.core.spec.chromosome.ChromosomeSpec;
import net.bmahe.genetics4j.neat.Connection;
import net.bmahe.genetics4j.neat.InnovationManager;
import net.bmahe.genetics4j.neat.chromosomes.NeatChromosome;
import net.bmahe.genetics4j.neat.spec.NeatChromosomeSpec;

/**
 * Factory for creating fully-connected initial NEAT (NeuroEvolution of Augmenting Topologies) chromosomes.
 * 
 * <p>NeatConnectedChromosomeFactory generates initial neural network chromosomes with direct connections
 * between all input and output nodes. This provides a minimal starting topology that ensures all inputs
 * can influence all outputs, creating a foundation for structural evolution through the NEAT algorithm.
 * 
 * <p>Generated network characteristics:
 * <ul>
 * <li><strong>Full connectivity</strong>: Every input node connected to every output node</li>
 * <li><strong>No hidden nodes</strong>: Initial networks contain only input and output layers</li>
 * <li><strong>Random weights</strong>: Connection weights uniformly distributed within specified bounds</li>
 * <li><strong>Innovation tracking</strong>: All connections assigned unique innovation numbers</li>
 * </ul>
 * 
 * <p>Network topology structure:
 * <ul>
 * <li><strong>Input layer</strong>: Nodes 0 to (numInputs - 1)</li>
 * <li><strong>Output layer</strong>: Nodes numInputs to (numInputs + numOutputs - 1)</li>
 * <li><strong>Connections</strong>: numInputs × numOutputs fully-connected bipartite graph</li>
 * <li><strong>Enabled state</strong>: All initial connections are enabled</li>
 * </ul>
 * 
 * <p>Common usage patterns:
 * <pre>{@code
 * // Create factory with innovation manager
 * RandomGenerator randomGen = RandomGenerator.getDefault();
 * InnovationManager innovationManager = new InnovationManager();
 * NeatConnectedChromosomeFactory factory = new NeatConnectedChromosomeFactory(
 *     randomGen, innovationManager
 * );
 * 
 * // Define network specification
 * NeatChromosomeSpec spec = NeatChromosomeSpec.of(
 *     3,      // 3 input nodes
 *     2,      // 2 output nodes  
 *     -1.0f,  // minimum weight
 *     1.0f    // maximum weight
 * );
 * 
 * // Generate initial chromosome
 * NeatChromosome chromosome = factory.generate(spec);
 * 
 * // Result: 3×2 = 6 connections with random weights
 * // Connections: (0→3), (0→4), (1→3), (1→4), (2→3), (2→4)
 * }</pre>
 * 
 * <p>Integration with NEAT evolution:
 * <ul>
 * <li><strong>Population initialization</strong>: Creates diverse initial population with same topology</li>
 * <li><strong>Weight diversity</strong>: Random weights provide behavioral variation</li>
 * <li><strong>Structural foundation</strong>: Minimal topology allows maximum structural exploration</li>
 * <li><strong>Innovation consistency</strong>: Same connection types get same innovation numbers across population</li>
 * </ul>
 * 
 * <p>Innovation number management:
 * <ul>
 * <li><strong>Deterministic assignment</strong>: Same input-output pairs get same innovation numbers</li>
 * <li><strong>Population consistency</strong>: All individuals use same innovation numbers for same connections</li>
 * <li><strong>Crossover compatibility</strong>: Enables meaningful genetic recombination from generation 0</li>
 * <li><strong>Historical tracking</strong>: Foundation for tracking structural evolution</li>
 * </ul>
 * 
 * <p>Weight initialization strategy:
 * <ul>
 * <li><strong>Uniform distribution</strong>: Weights uniformly sampled from [minWeight, maxWeight]</li>
 * <li><strong>Behavioral diversity</strong>: Different weight combinations create different behaviors</li>
 * <li><strong>Network stability</strong>: Bounded weights prevent extreme activation values</li>
 * <li><strong>Evolution readiness</strong>: Initial weights suitable for gradient-based optimization</li>
 * </ul>
 * 
 * <p>Performance considerations:
 * <ul>
 * <li><strong>Linear time complexity</strong>: O(numInputs × numOutputs) generation time</li>
 * <li><strong>Memory efficiency</strong>: Minimal memory allocation during generation</li>
 * <li><strong>Innovation caching</strong>: InnovationManager provides O(1) innovation number lookup</li>
 * <li><strong>Thread safety</strong>: Safe for concurrent chromosome generation</li>
 * </ul>
 * 
 * @see NeatChromosome
 * @see NeatChromosomeSpec
 * @see InnovationManager
 * @see ChromosomeFactory
 */
public class NeatConnectedChromosomeFactory implements ChromosomeFactory<NeatChromosome> {

	private final RandomGenerator randomGenerator;
	private final InnovationManager innovationManager;

	/**
	 * Constructs a new connected chromosome factory with the specified components.
	 * 
	 * <p>The random generator is used for weight initialization, providing behavioral
	 * diversity in the initial population. The innovation manager ensures consistent
	 * innovation number assignment across all generated chromosomes.
	 * 
	 * @param _randomGenerator random number generator for weight initialization
	 * @param _innovationManager innovation manager for tracking structural innovations
	 * @throws IllegalArgumentException if randomGenerator or innovationManager is null
	 */
	public NeatConnectedChromosomeFactory(final RandomGenerator _randomGenerator,
			final InnovationManager _innovationManager) {
		Validate.notNull(_randomGenerator);
		Validate.notNull(_innovationManager);

		this.randomGenerator = _randomGenerator;
		this.innovationManager = _innovationManager;
	}

	/**
	 * Determines whether this factory can generate chromosomes for the given specification.
	 * 
	 * <p>This factory specifically handles NeatChromosomeSpec specifications, which define
	 * the input/output structure and weight bounds for NEAT neural networks.
	 * 
	 * @param chromosomeSpec the chromosome specification to check
	 * @return true if chromosomeSpec is a NeatChromosomeSpec, false otherwise
	 * @throws IllegalArgumentException if chromosomeSpec is null
	 */
	@Override
	public boolean canHandle(final ChromosomeSpec chromosomeSpec) {
		Validate.notNull(chromosomeSpec);

		return chromosomeSpec instanceof NeatChromosomeSpec;
	}

	/**
	 * Generates a fully-connected NEAT chromosome based on the given specification.
	 * 
	 * <p>This method creates a neural network chromosome with direct connections between
	 * all input and output nodes. Each connection is initialized with a random weight
	 * within the specified bounds and assigned a unique innovation number for genetic
	 * tracking.
	 * 
	 * <p>Generation process:
	 * <ol>
	 * <li>Extract network parameters from the chromosome specification</li>
	 * <li>Create connections between all input-output node pairs</li>
	 * <li>Assign innovation numbers to each connection type</li>
	 * <li>Initialize connection weights randomly within bounds</li>
	 * <li>Enable all connections for immediate network functionality</li>
	 * <li>Construct and return the complete chromosome</li>
	 * </ol>
	 * 
	 * @param chromosomeSpec the NEAT chromosome specification defining network structure
	 * @return a new fully-connected NEAT chromosome
	 * @throws IllegalArgumentException if chromosomeSpec is null or not a NeatChromosomeSpec
	 */
	@Override
	public NeatChromosome generate(final ChromosomeSpec chromosomeSpec) {
		Validate.notNull(chromosomeSpec);
		Validate.isInstanceOf(NeatChromosomeSpec.class, chromosomeSpec);

		final NeatChromosomeSpec neatChromosomeSpec = (NeatChromosomeSpec) chromosomeSpec;
		final int numInputs = neatChromosomeSpec.numInputs();
		final int numOutputs = neatChromosomeSpec.numOutputs();
		float minWeightValue = neatChromosomeSpec.minWeightValue();
		float maxWeightValue = neatChromosomeSpec.maxWeightValue();

		final List<Connection> connections = new ArrayList<>();
		for (int inputIndex = 0; inputIndex < numInputs; inputIndex++) {
			for (int outputIndex = numInputs; outputIndex < numInputs + numOutputs; outputIndex++) {

				final int innovation = innovationManager.computeNewId(inputIndex, outputIndex);
				final Connection connection = Connection.builder()
						.fromNodeIndex(inputIndex)
						.toNodeIndex(outputIndex)
						.innovation(innovation)
						.isEnabled(true)
						.weight(randomGenerator.nextFloat(minWeightValue, maxWeightValue))
						.build();
				connections.add(connection);
			}
		}

		return new NeatChromosome(numInputs, numOutputs, minWeightValue, maxWeightValue, connections);
	}
}