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 }