NeatChromosome.java

package net.bmahe.genetics4j.neat.chromosomes;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import org.apache.commons.lang3.Validate;

import net.bmahe.genetics4j.core.chromosomes.Chromosome;
import net.bmahe.genetics4j.neat.Connection;
import net.bmahe.genetics4j.neat.spec.NeatNodeLayout;

/**
 * Represents a neural network chromosome in the NEAT (NeuroEvolution of Augmenting Topologies) algorithm.
 * 
 * <p>NeatChromosome is the core genetic representation in NEAT, encoding a neural network as a collection of
 * connections between nodes. Each chromosome defines a complete neural network topology with input nodes, output nodes,
 * optional hidden nodes, and weighted connections. The chromosome maintains essential parameters for network
 * construction and genetic operations.
 * 
 * <p>Key characteristics:
 * <ul>
 * <li><strong>Network topology</strong>: Encoded as a list of connections with innovation numbers</li>
 * <li><strong>Node organization</strong>: Fixed input/output nodes with dynamically added hidden nodes</li>
 * <li><strong>Weight constraints</strong>: Configurable minimum and maximum weight bounds</li>
 * <li><strong>Innovation tracking</strong>: Connections sorted by innovation number for genetic alignment</li>
 * </ul>
 * 
 * <p>NEAT algorithm integration:
 * <ul>
 * <li><strong>Structural mutations</strong>: Add/delete nodes and connections while preserving innovation tracking</li>
 * <li><strong>Weight mutations</strong>: Modify connection weights within specified bounds</li>
 * <li><strong>Genetic crossover</strong>: Innovation-number-based gene alignment for topology recombination</li>
 * <li><strong>Compatibility distance</strong>: Genetic similarity measurement for speciation</li>
 * </ul>
 * 
 * <p>Network structure:
 * <ul>
 * <li><strong>Input and output nodes</strong>: Stable ordered IDs declared by the node layout</li>
 * <li><strong>Hidden nodes</strong>: IDs allocated from the layout's hidden-node namespace</li>
 * <li><strong>Connections</strong>: Weighted links between nodes with enable/disable states and innovation numbers</li>
 * </ul>
 * 
 * <p>Common usage patterns:
 * 
 * <pre>{@code
 * // Create a basic NEAT chromosome
 * List<Connection> connections = List.of(
 * 		Connection.of(0, 2, 0.5f, true, 0), // input 0 -> output 0
 * 			Connection.of(1, 3, -0.3f, true, 1) // input 1 -> output 1
 * );
 * 
 * NeatChromosome chromosome = new NeatChromosome(2, // number of inputs
 * 		2, // number of outputs
 * 		-1.0f, // minimum weight
 * 		1.0f, // maximum weight
 * 		connections);
 * 
 * // Access chromosome properties
 * int numAlleles = chromosome.getNumAlleles();
 * List<Integer> inputNodes = chromosome.getInputNodeIds();
 * List<Integer> outputNodes = chromosome.getOutputNodeIds();
 * List<Connection> allConnections = chromosome.getConnections();
 * 
 * // Create feed-forward network for evaluation
 * FeedForwardNetwork network = new FeedForwardNetwork(Set.copyOf(chromosome.getInputNodeIds()),
 * 		Set.copyOf(chromosome.getOutputNodeIds()),
 * 		chromosome.getConnections(),
 * 		Activations::sigmoid);
 * }</pre>
 * 
 * <p>Genetic operations compatibility:
 * <ul>
 * <li><strong>Mutation operations</strong>: Compatible with weight, add-node, add-connection, and state mutations</li>
 * <li><strong>Crossover operations</strong>: Innovation numbers enable proper gene alignment between parents</li>
 * <li><strong>Selection operations</strong>: Supports species-based selection through compatibility distance</li>
 * <li><strong>Evaluation operations</strong>: Can be converted to executable neural networks</li>
 * </ul>
 * 
 * <p>Innovation number organization:
 * <ul>
 * <li><strong>Sorted connections</strong>: Connections automatically sorted by innovation number</li>
 * <li><strong>Genetic alignment</strong>: Enables efficient crossover and compatibility calculations</li>
 * <li><strong>Historical tracking</strong>: Maintains evolutionary history of structural changes</li>
 * <li><strong>Population consistency</strong>: Same innovation numbers across population for same connection types</li>
 * </ul>
 * 
 * <p>Performance considerations:
 * <ul>
 * <li><strong>Immutable connections</strong>: Connection list is sorted once and made immutable</li>
 * <li><strong>Efficient lookup</strong>: Node indices computed deterministically for fast access</li>
 * <li><strong>Memory efficiency</strong>: Only stores necessary network topology information</li>
 * <li><strong>Cache-friendly</strong>: Sorted connections improve cache locality for genetic operations</li>
 * </ul>
 * 
 * <p>Integration with NEAT ecosystem:
 * <ul>
 * <li><strong>Chromosome factories</strong>: Created by NeatConnectedChromosomeFactory and similar</li>
 * <li><strong>Genetic operators</strong>: Processed by NEAT-specific mutation and crossover handlers</li>
 * <li><strong>Network evaluation</strong>: Converted to FeedForwardNetwork for fitness computation</li>
 * <li><strong>Speciation</strong>: Used in compatibility distance calculations for species formation</li>
 * </ul>
 * 
 * @see Connection
 * @see FeedForwardNetwork
 * @see net.bmahe.genetics4j.neat.RecurrentNetwork
 * @see InnovationManager
 * @see net.bmahe.genetics4j.neat.spec.NeatChromosomeSpec
 */
public class NeatChromosome implements Chromosome {

	private final NeatNodeLayout nodeLayout;
	private final float minWeightValue;
	private final float maxWeightValue;
	private final List<Connection> connections;

	/**
	 * Constructs a new NEAT chromosome with the specified network topology and parameters.
	 * 
	 * <p>This constructor creates an immutable neural network chromosome by copying and sorting the provided connections
	 * by their innovation numbers. The sorting ensures efficient genetic operations and proper gene alignment during
	 * crossover operations.
	 * 
	 * <p>Network structure validation:
	 * <ul>
	 * <li>The node layout must be valid</li>
	 * <li>Weight bounds must be properly ordered (min &lt; max)</li>
	 * <li>Connections list must not be null (but can be empty)</li>
	 * <li>Connection endpoints must belong to the external or hidden-node namespaces</li>
	 * </ul>
	 * 
	 * @param _nodeLayout     stable external IDs and hidden-node namespace
	 * @param _minWeightValue minimum allowed connection weight value
	 * @param _maxWeightValue maximum allowed connection weight value (must be &gt; minWeightValue)
	 * @param _connections    list of network connections (will be copied and sorted by innovation number)
	 * @throws IllegalArgumentException if minWeightValue &gt;= maxWeightValue
	 */
	public NeatChromosome(final NeatNodeLayout _nodeLayout,
			final float _minWeightValue,
			final float _maxWeightValue,
			final List<Connection> _connections) {
		Validate.notNull(_nodeLayout);
		Validate.isTrue(_minWeightValue < _maxWeightValue);
		Validate.notNull(_connections);
		for (final Connection connection : _connections) {
			Validate.notNull(connection);
			Validate.isTrue(
					_nodeLayout.isExternal(connection.fromNodeIndex()) || _nodeLayout.isHidden(connection.fromNodeIndex()),
						"Connection source node %d is outside the node layout",
						connection.fromNodeIndex());
			Validate.isTrue(
					_nodeLayout.isExternal(connection.toNodeIndex()) || _nodeLayout.isHidden(connection.toNodeIndex()),
						"Connection target node %d is outside the node layout",
						connection.toNodeIndex());
		}

		this.nodeLayout = _nodeLayout;
		this.minWeightValue = _minWeightValue;
		this.maxWeightValue = _maxWeightValue;

		final List<Connection> copyOfConnections = new ArrayList<>(_connections);
		Collections.sort(copyOfConnections, Comparator.comparing(Connection::innovation));
		this.connections = Collections.unmodifiableList(copyOfConnections);
	}

	/** Convenience constructor for the traditional contiguous node layout. */
	public NeatChromosome(final int numInputs,
			final int numOutputs,
			final float minWeightValue,
			final float maxWeightValue,
			final List<Connection> connections) {
		this(NeatNodeLayout.contiguous(numInputs, numOutputs), minWeightValue, maxWeightValue, connections);
	}

	/**
	 * Returns the total number of alleles (genetic components) in this chromosome.
	 * 
	 * <p>For NEAT chromosomes, the allele count includes:
	 * <ul>
	 * <li>Input nodes: Each input node represents one allele</li>
	 * <li>Output nodes: Each output node represents one allele</li>
	 * <li>Connections: Each connection (with its weight and state) represents one allele</li>
	 * </ul>
	 * 
	 * <p>Hidden nodes are not counted separately as they are implicit in the connection structure. This count is used by
	 * the genetic algorithm framework for population statistics and compatibility calculations.
	 * 
	 * @return the total number of alleles in this chromosome
	 */
	@Override
	public int getNumAlleles() {
		return nodeLayout.numInputs() + nodeLayout.numOutputs() + connections.size();
	}

	/**
	 * Returns the number of input nodes in this neural network.
	 * 
	 * <p>Input nodes are the ordered entry points declared by the node layout.
	 * 
	 * @return the number of input nodes (always positive)
	 */
	public int getNumInputs() {
		return nodeLayout.numInputs();
	}

	/**
	 * Returns the number of output nodes in this neural network.
	 * 
	 * <p>Output nodes are the ordered result nodes declared by the node layout.
	 * 
	 * @return the number of output nodes (always positive)
	 */
	public int getNumOutputs() {
		return nodeLayout.numOutputs();
	}

	public NeatNodeLayout getNodeLayout() {
		return nodeLayout;
	}

	/** Returns input node IDs in their declared vector order. */
	public List<Integer> getInputNodeIds() {
		return nodeLayout.inputNodeIds();
	}

	/** Returns output node IDs in their declared vector order. */
	public List<Integer> getOutputNodeIds() {
		return nodeLayout.outputNodeIds();
	}

	/**
	 * Returns the minimum allowed connection weight value for this network.
	 * 
	 * <p>This bound is used by mutation operators to constrain weight perturbations and ensure that connection weights
	 * remain within reasonable ranges. Weight mutations should respect this bound to maintain network stability.
	 * 
	 * @return the minimum allowed connection weight
	 */
	public float getMinWeightValue() {
		return minWeightValue;
	}

	/**
	 * Returns the maximum allowed connection weight value for this network.
	 * 
	 * <p>This bound is used by mutation operators to constrain weight perturbations and ensure that connection weights
	 * remain within reasonable ranges. Weight mutations should respect this bound to maintain network stability.
	 * 
	 * @return the maximum allowed connection weight
	 */
	public float getMaxWeightValue() {
		return maxWeightValue;
	}

	/**
	 * Returns an immutable list of all connections in this neural network.
	 * 
	 * <p>The connections are sorted by innovation number to ensure consistent ordering for genetic operations. Each
	 * connection defines a weighted link between two nodes and includes an enabled/disabled state for topology
	 * exploration.
	 * 
	 * <p>Connection properties:
	 * <ul>
	 * <li><strong>Immutable ordering</strong>: Connections are sorted by innovation number</li>
	 * <li><strong>Complete topology</strong>: Includes both enabled and disabled connections</li>
	 * <li><strong>Genetic information</strong>: Each connection carries innovation tracking data</li>
	 * <li><strong>Network structure</strong>: Defines the complete computational graph</li>
	 * </ul>
	 * 
	 * @return immutable list of network connections, sorted by innovation number
	 */
	public List<Connection> getConnections() {
		return connections;
	}

	/**
	 * Returns the input IDs as a set, discarding declaration order.
	 * 
	 * @return set of input node IDs
	 * @deprecated use {@link #getInputNodeIds()} to preserve declaration order
	 */
	@Deprecated(forRemoval = false)
	public Set<Integer> getInputNodeIndices() {
		return Set.copyOf(nodeLayout.inputNodeIds());
	}

	/**
	 * Returns the output IDs as a set, discarding declaration order.
	 * 
	 * @return set of output node IDs
	 * @deprecated use {@link #getOutputNodeIds()} to preserve declaration order
	 */
	@Deprecated(forRemoval = false)
	public Set<Integer> getOutputNodeIndices() {
		return Set.copyOf(nodeLayout.outputNodeIds());
	}

	@Override
	public int hashCode() {
		return Objects.hash(connections, maxWeightValue, minWeightValue, nodeLayout);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		NeatChromosome other = (NeatChromosome) obj;
		return Objects.equals(connections, other.connections)
				&& Float.floatToIntBits(maxWeightValue) == Float.floatToIntBits(other.maxWeightValue)
				&& Float.floatToIntBits(minWeightValue) == Float.floatToIntBits(other.minWeightValue)
				&& nodeLayout.equals(other.nodeLayout);
	}

	@Override
	public String toString() {
		return "NeatChromosome [nodeLayout=" + nodeLayout + ", minWeightValue=" + minWeightValue + ", maxWeightValue="
				+ maxWeightValue + ", connections=" + connections + "]";
	}
}