View Javadoc
1   package net.bmahe.genetics4j.neat.chromosomes.factory;
2   
3   import java.util.ArrayList;
4   import java.util.List;
5   import java.util.Objects;
6   import java.util.random.RandomGenerator;
7   
8   import org.apache.commons.lang3.Validate;
9   
10  import net.bmahe.genetics4j.core.chromosomes.factory.ChromosomeFactory;
11  import net.bmahe.genetics4j.core.spec.chromosome.ChromosomeSpec;
12  import net.bmahe.genetics4j.neat.Connection;
13  import net.bmahe.genetics4j.neat.InnovationManager;
14  import net.bmahe.genetics4j.neat.chromosomes.NeatChromosome;
15  import net.bmahe.genetics4j.neat.spec.NeatChromosomeSpec;
16  
17  /**
18   * Factory for creating fully-connected initial NEAT (NeuroEvolution of Augmenting Topologies) chromosomes.
19   * 
20   * <p>NeatConnectedChromosomeFactory generates initial neural network chromosomes with direct connections between all
21   * input and output nodes. This provides a minimal starting topology that ensures all inputs can influence all outputs,
22   * creating a foundation for structural evolution through the NEAT algorithm.
23   * 
24   * <p>Generated network characteristics:
25   * <ul>
26   * <li><strong>Full connectivity</strong>: Every input node connected to every output node</li>
27   * <li><strong>No hidden nodes</strong>: Initial networks contain only input and output layers</li>
28   * <li><strong>Random weights</strong>: Connection weights uniformly distributed within specified bounds</li>
29   * <li><strong>Innovation tracking</strong>: All connections assigned unique innovation numbers</li>
30   * </ul>
31   * 
32   * <p>Network topology structure:
33   * <ul>
34   * <li><strong>Input layer</strong>: Input IDs declared by the node layout</li>
35   * <li><strong>Output layer</strong>: Output IDs declared by the node layout</li>
36   * <li><strong>Connections</strong>: numInputs × numOutputs fully-connected bipartite graph</li>
37   * <li><strong>Enabled state</strong>: All initial connections are enabled</li>
38   * </ul>
39   * 
40   * <p>Common usage patterns:
41   * 
42   * <pre>{@code
43   * // Create factory with innovation manager
44   * RandomGenerator randomGen = RandomGenerator.getDefault();
45   * InnovationManager innovationManager = new InnovationManager();
46   * NeatConnectedChromosomeFactory factory = new NeatConnectedChromosomeFactory(randomGen, innovationManager);
47   * 
48   * // Define network specification
49   * NeatChromosomeSpec spec = NeatChromosomeSpec.of(
50   * 		3, // 3 input nodes
51   * 		2, // 2 output nodes
52   * 		-1.0f, // minimum weight
53   * 		1.0f // maximum weight
54   * );
55   * 
56   * // Generate initial chromosome
57   * NeatChromosome chromosome = factory.generate(spec);
58   * 
59   * // Result: 3×2 = 6 connections with random weights
60   * // Connections: (0→3), (0→4), (1→3), (1→4), (2→3), (2→4)
61   * }</pre>
62   * 
63   * <p>Integration with NEAT evolution:
64   * <ul>
65   * <li><strong>Population initialization</strong>: Creates diverse initial population with same topology</li>
66   * <li><strong>Weight diversity</strong>: Random weights provide behavioral variation</li>
67   * <li><strong>Structural foundation</strong>: Minimal topology allows maximum structural exploration</li>
68   * <li><strong>Innovation consistency</strong>: Same connection types get same innovation numbers across population</li>
69   * </ul>
70   * 
71   * <p>Innovation number management:
72   * <ul>
73   * <li><strong>Deterministic assignment</strong>: Same input-output pairs get same innovation numbers</li>
74   * <li><strong>Population consistency</strong>: All individuals use same innovation numbers for same connections</li>
75   * <li><strong>Crossover compatibility</strong>: Enables meaningful genetic recombination from generation 0</li>
76   * <li><strong>Historical tracking</strong>: Foundation for tracking structural evolution</li>
77   * </ul>
78   * 
79   * <p>Weight initialization strategy:
80   * <ul>
81   * <li><strong>Uniform distribution</strong>: Weights uniformly sampled from [minWeight, maxWeight]</li>
82   * <li><strong>Behavioral diversity</strong>: Different weight combinations create different behaviors</li>
83   * <li><strong>Network stability</strong>: Bounded weights prevent extreme activation values</li>
84   * <li><strong>Evolution readiness</strong>: Initial weights suitable for gradient-based optimization</li>
85   * </ul>
86   * 
87   * <p>Performance considerations:
88   * <ul>
89   * <li><strong>Linear time complexity</strong>: O(numInputs × numOutputs) generation time</li>
90   * <li><strong>Memory efficiency</strong>: Minimal memory allocation during generation</li>
91   * <li><strong>Innovation caching</strong>: InnovationManager provides O(1) innovation number lookup</li>
92   * <li><strong>Thread safety</strong>: Safe for concurrent chromosome generation</li>
93   * </ul>
94   * 
95   * @see NeatChromosome
96   * @see NeatChromosomeSpec
97   * @see InnovationManager
98   * @see ChromosomeFactory
99   */
100 public class NeatConnectedChromosomeFactory implements ChromosomeFactory<NeatChromosome> {
101 
102 	private final RandomGenerator randomGenerator;
103 	private final InnovationManager innovationManager;
104 
105 	/**
106 	 * Constructs a new connected chromosome factory with the specified components.
107 	 * 
108 	 * <p>The random generator is used for weight initialization, providing behavioral diversity in the initial
109 	 * population. The innovation manager ensures consistent innovation number assignment across all generated
110 	 * chromosomes.
111 	 * 
112 	 * @param _randomGenerator   random number generator for weight initialization
113 	 * @param _innovationManager innovation manager for tracking structural innovations
114 	 * @throws IllegalArgumentException if randomGenerator or innovationManager is null
115 	 */
116 	public NeatConnectedChromosomeFactory(final RandomGenerator _randomGenerator,
117 			final InnovationManager _innovationManager) {
118 		Objects.requireNonNull(_randomGenerator);
119 		Objects.requireNonNull(_innovationManager);
120 
121 		this.randomGenerator = _randomGenerator;
122 		this.innovationManager = _innovationManager;
123 	}
124 
125 	/**
126 	 * Determines whether this factory can generate chromosomes for the given specification.
127 	 * 
128 	 * <p>This factory specifically handles NeatChromosomeSpec specifications, which define the input/output structure
129 	 * and weight bounds for NEAT neural networks.
130 	 * 
131 	 * @param chromosomeSpec the chromosome specification to check
132 	 * @return true if chromosomeSpec is a NeatChromosomeSpec, false otherwise
133 	 * @throws IllegalArgumentException if chromosomeSpec is null
134 	 */
135 	@Override
136 	public boolean canHandle(final ChromosomeSpec chromosomeSpec) {
137 		Objects.requireNonNull(chromosomeSpec);
138 
139 		return chromosomeSpec instanceof NeatChromosomeSpec;
140 	}
141 
142 	/**
143 	 * Generates a fully-connected NEAT chromosome based on the given specification.
144 	 * 
145 	 * <p>This method creates a neural network chromosome with direct connections between all input and output nodes.
146 	 * Each connection is initialized with a random weight within the specified bounds and assigned a unique innovation
147 	 * number for genetic tracking.
148 	 * 
149 	 * <p>Generation process:
150 	 * <ol>
151 	 * <li>Extract network parameters from the chromosome specification</li>
152 	 * <li>Create connections between all input-output node pairs</li>
153 	 * <li>Assign innovation numbers to each connection type</li>
154 	 * <li>Initialize connection weights randomly within bounds</li>
155 	 * <li>Enable all connections for immediate network functionality</li>
156 	 * <li>Construct and return the complete chromosome</li>
157 	 * </ol>
158 	 * 
159 	 * @param chromosomeSpec the NEAT chromosome specification defining network structure
160 	 * @return a new fully-connected NEAT chromosome
161 	 * @throws IllegalArgumentException if chromosomeSpec is null or not a NeatChromosomeSpec
162 	 */
163 	@Override
164 	public NeatChromosome generate(final ChromosomeSpec chromosomeSpec) {
165 		Objects.requireNonNull(chromosomeSpec);
166 		Validate.isInstanceOf(NeatChromosomeSpec.class, chromosomeSpec);
167 
168 		final NeatChromosomeSpec neatChromosomeSpec = (NeatChromosomeSpec) chromosomeSpec;
169 		final var nodeLayout = neatChromosomeSpec.nodeLayout();
170 		float minWeightValue = neatChromosomeSpec.minWeightValue();
171 		float maxWeightValue = neatChromosomeSpec.maxWeightValue();
172 
173 		final List<Connection> connections = new ArrayList<>();
174 		for (final int inputIndex : nodeLayout.inputNodeIds()) {
175 			for (final int outputIndex : nodeLayout.outputNodeIds()) {
176 
177 				final int innovation = innovationManager.computeNewId(inputIndex, outputIndex);
178 				final Connection connection = Connection.builder()
179 						.fromNodeIndex(inputIndex)
180 						.toNodeIndex(outputIndex)
181 						.innovation(innovation)
182 						.isEnabled(true)
183 						.weight(randomGenerator.nextFloat(minWeightValue, maxWeightValue))
184 						.build();
185 				connections.add(connection);
186 			}
187 		}
188 
189 		return new NeatChromosome(nodeLayout, minWeightValue, maxWeightValue, connections);
190 	}
191 }