1 package net.bmahe.genetics4j.neat.spec.selection;
2
3 import java.util.function.BiPredicate;
4
5 import org.apache.commons.lang3.Validate;
6 import org.immutables.value.Value;
7
8 import net.bmahe.genetics4j.core.Individual;
9 import net.bmahe.genetics4j.core.spec.selection.SelectionPolicy;
10 import net.bmahe.genetics4j.core.spec.selection.Tournament;
11 import net.bmahe.genetics4j.neat.NeatUtils;
12
13 /**
14 * Selection policy for NEAT (NeuroEvolution of Augmenting Topologies) species-based selection.
15 *
16 * <p>NeatSelection implements the species-based selection mechanism that is fundamental to NEAT's ability to maintain
17 * population diversity and protect structural innovations. It organizes the population into species based on genetic
18 * compatibility, applies fitness sharing within species, and manages reproduction allocation to prevent dominant
19 * topologies from eliminating exploration.
20 *
21 * <p>Key features:
22 * <ul>
23 * <li><strong>Species formation</strong>: Groups genetically similar individuals using compatibility predicates</li>
24 * <li><strong>Fitness sharing</strong>: Reduces fitness pressure within species to promote diversity</li>
25 * <li><strong>Species preservation</strong>: Maintains minimum viable species sizes</li>
26 * <li><strong>Reproduction allocation</strong>: Distributes offspring based on species average fitness</li>
27 * </ul>
28 *
29 * <p>NEAT species-based selection process:
30 * <ol>
31 * <li><strong>Compatibility testing</strong>: Apply species predicate to group similar individuals</li>
32 * <li><strong>Species assignment</strong>: Assign individuals to species based on genetic distance</li>
33 * <li><strong>Fitness adjustment</strong>: Apply fitness sharing within each species</li>
34 * <li><strong>Species filtering</strong>: Remove species below minimum size threshold</li>
35 * <li><strong>Reproduction allocation</strong>: Determine offspring count per species</li>
36 * <li><strong>Within-species selection</strong>: Select parents using specified selection policy</li>
37 * </ol>
38 *
39 * <p>Species management parameters:
40 * <ul>
41 * <li><strong>Keep ratio</strong>: Proportion of each species to preserve for reproduction</li>
42 * <li><strong>Minimum size</strong>: Smallest viable species size to prevent extinction</li>
43 * <li><strong>Compatibility predicate</strong>: Function determining species membership</li>
44 * <li><strong>Selection policy</strong>: Within-species selection strategy</li>
45 * </ul>
46 *
47 * <p>Common usage patterns:
48 *
49 * <pre>{@code
50 * // Default NEAT selection with standard parameters
51 * NeatSelection<Double> defaultSelection = NeatSelection.ofDefault();
52 *
53 * // Custom compatibility threshold
54 * BiPredicate<Individual<Double>, Individual<Double>> compatibilityPredicate = (i1,
55 * i2) -> NeatUtils.compatibilityDistance(i1.genotype(), i2.genotype(), 0, 2, 2, 1.0f) < 3.0; // Higher threshold
56 * // = fewer, larger
57 * // species
58 *
59 * NeatSelection<Double> customSelection = NeatSelection.<Double>builder()
60 * .perSpeciesKeepRatio(0.8f) // Keep top 80% of each species
61 * .minSpeciesSize(3) // Minimum 3 individuals per species
62 * .speciesPredicate(compatibilityPredicate)
63 * .speciesSelection(Tournament.of(5)) // Tournament size 5 within species
64 * .build();
65 *
66 * // Aggressive diversity preservation
67 * NeatSelection<Double> diverseSelection = NeatSelection.of(
68 * 0.95f, // Keep 95% of each species
69 * compatibilityPredicate,
70 * new ProportionalSelection() // Proportional selection within species
71 * );
72 * }</pre>
73 *
74 * <p>Compatibility distance calculation:
75 * <ul>
76 * <li><strong>Matching genes</strong>: Genes with same innovation numbers in both individuals</li>
77 * <li><strong>Disjoint genes</strong>: Genes in one individual within the other's innovation range</li>
78 * <li><strong>Excess genes</strong>: Genes beyond the other individual's highest innovation number</li>
79 * <li><strong>Weight differences</strong>: Average difference in matching gene weights</li>
80 * </ul>
81 *
82 * <p>Species preservation strategies:
83 * <ul>
84 * <li><strong>Keep ratio</strong>: Ensures a proportion of each species survives selection pressure</li>
85 * <li><strong>Minimum size</strong>: Prevents viable species from going extinct due to random drift</li>
86 * <li><strong>Fitness sharing</strong>: Reduces competition between similar individuals</li>
87 * <li><strong>Innovation protection</strong>: Gives new topologies time to optimize</li>
88 * </ul>
89 *
90 * <p>Integration with genetic operators:
91 * <ul>
92 * <li><strong>Crossover compatibility</strong>: Species ensure genetic similarity for meaningful recombination</li>
93 * <li><strong>Mutation guidance</strong>: Species composition can influence mutation rates</li>
94 * <li><strong>Structural innovation</strong>: Protected evolution of different network topologies</li>
95 * <li><strong>Population dynamics</strong>: Species formation and extinction drive exploration</li>
96 * </ul>
97 *
98 * <p>Performance considerations:
99 * <ul>
100 * <li><strong>Compatibility caching</strong>: Distance calculations cached for efficiency</li>
101 * <li><strong>Species reuse</strong>: Species structures maintained across generations</li>
102 * <li><strong>Parallel evaluation</strong>: Species-based organization enables concurrent processing</li>
103 * <li><strong>Memory efficiency</strong>: Efficient species membership tracking</li>
104 * </ul>
105 *
106 * @param <T> the fitness value type (typically Double)
107 * @see SelectionPolicy
108 * @see NeatUtils#compatibilityDistance
109 * @see net.bmahe.genetics4j.neat.selection.NeatSelectionPolicyHandler
110 * @see net.bmahe.genetics4j.neat.Species
111 */
112 @Value.Immutable
113 public abstract class NeatSelection<T extends Comparable<T>> implements SelectionPolicy {
114
115 /**
116 * Returns the proportion of each species to preserve for reproduction.
117 *
118 * <p>This ratio determines what fraction of each species will be retained after fitness-based culling. Higher values
119 * preserve more diversity within species but may slow convergence, while lower values increase selection pressure
120 * but may lose beneficial genetic variations.
121 *
122 * <p>Typical values:
123 * <ul>
124 * <li><strong>0.9 (default)</strong>: Preserve top 90% of each species</li>
125 * <li><strong>0.8-0.95</strong>: Balanced diversity preservation</li>
126 * <li><strong>< 0.8</strong>: Aggressive selection pressure</li>
127 * <li><strong>> 0.95</strong>: Minimal selection pressure, maximum diversity</li>
128 * </ul>
129 *
130 * @return keep ratio between 0.0 and 1.0 (exclusive of 0.0, inclusive of 1.0)
131 */
132 @Value.Default
133 public float perSpeciesKeepRatio() {
134 return 0.90f;
135 }
136
137 /**
138 * Returns the minimum number of individuals required to maintain a species.
139 *
140 * <p>Species with fewer members than this threshold will be eliminated to prevent resource waste on non-viable
141 * populations. This helps focus evolutionary resources on species with sufficient genetic diversity to explore their
142 * local fitness landscape effectively.
143 *
144 * <p>Typical values:
145 * <ul>
146 * <li><strong>5 (default)</strong>: Balanced viability threshold</li>
147 * <li><strong>3-10</strong>: Reasonable range for most problems</li>
148 * <li><strong>< 3</strong>: Very permissive, allows small species to survive</li>
149 * <li><strong>> 10</strong>: Strict threshold, eliminates marginal species</li>
150 * </ul>
151 *
152 * @return minimum species size (must be positive)
153 */
154 @Value.Default
155 public int minSpeciesSize() {
156 return 5;
157 }
158
159 /**
160 * Returns the predicate used to determine species membership.
161 *
162 * <p>This bi-predicate takes two individuals and returns true if they should belong to the same species based on
163 * their genetic compatibility. Typically implemented using NEAT compatibility distance with a threshold value.
164 *
165 * <p>Common implementations:
166 * <ul>
167 * <li><strong>Compatibility distance</strong>: Based on matching, disjoint, excess genes and weight differences</li>
168 * <li><strong>Topological similarity</strong>: Based on network structure similarity</li>
169 * <li><strong>Behavioral similarity</strong>: Based on network output patterns</li>
170 * <li><strong>Custom metrics</strong>: Domain-specific similarity measures</li>
171 * </ul>
172 *
173 * @return bi-predicate for determining species membership
174 */
175 public abstract BiPredicate<Individual<T>, Individual<T>> speciesPredicate();
176
177 /**
178 * Returns the selection policy used within each species.
179 *
180 * <p>After individuals are organized into species, this policy determines how parents are selected within each
181 * species for reproduction. Common choices include tournament selection, proportional selection, or rank-based
182 * selection.
183 *
184 * <p>Selection policy considerations:
185 * <ul>
186 * <li><strong>Tournament selection</strong>: Good balance of selection pressure and diversity</li>
187 * <li><strong>Proportional selection</strong>: Fitness-proportionate selection within species</li>
188 * <li><strong>Rank selection</strong>: Rank-based selection to avoid fitness scaling issues</li>
189 * <li><strong>Elite selection</strong>: Always select best individuals within species</li>
190 * </ul>
191 *
192 * @return selection policy for within-species parent selection
193 */
194 public abstract SelectionPolicy speciesSelection();
195
196 /**
197 * Returns the probability of mating between individuals from different species.
198 *
199 * <p>Standard NEAT (Stanley & Miikkulainen, 2002) speciation isolates species for mating to protect structural
200 * innovations. The original paper allows a small probability of interspecies mating (e.g. 0.001) to enable
201 * occasional genetic mixing across species boundaries.
202 *
203 * <p>The selector returns a flat population ordered by species. {@code EASystem} pairs consecutive individuals
204 * (index 0+1, 2+3, ...). When this rate is 0, each species allocation is rounded to an even count so that every
205 * consecutive pair stays within the same species. When the rate is greater than 0, the selector randomly swaps
206 * the second individual of some pairs with an individual from a different species, at the given probability.
207 *
208 * <p>Typical values:
209 * <ul>
210 * <li><strong>0.0 (default)</strong>: Strict speciation, no interspecies mating</li>
211 * <li><strong>0.001</strong>: Paper's suggested rate, occasional cross-species pairing</li>
212 * <li><strong>0.05-0.1</strong>: Aggressive genetic mixing</li>
213 * </ul>
214 *
215 * @return interspecies mating rate between 0.0 (inclusive) and 1.0 (inclusive)
216 */
217 @Value.Default
218 public float interspeciesMatingRate() {
219 return 0.0f;
220 }
221
222 @Value.Check
223 public void check() {
224 Validate.inclusiveBetween(0.0f, 1.0f, perSpeciesKeepRatio());
225 Validate.isTrue(perSpeciesKeepRatio() > 0.0f);
226 Validate.inclusiveBetween(0.0f, 1.0f, interspeciesMatingRate());
227 }
228
229 public static class Builder<T extends Comparable<T>> extends ImmutableNeatSelection.Builder<T> {
230 }
231
232 public static <U extends Comparable<U>> Builder<U> builder() {
233 return new Builder<>();
234 }
235
236 /**
237 * Creates a NEAT selection policy with custom keep ratio and specified parameters.
238 *
239 * @param <U> the fitness value type
240 * @param perSpeciesKeepRatio proportion of each species to preserve (0.0 < ratio <= 1.0)
241 * @param speciesPredicate predicate for determining species membership
242 * @param speciesSelection selection policy for within-species parent selection
243 * @return a new NEAT selection policy with the specified parameters
244 */
245 public static <U extends Comparable<U>> NeatSelection<U> of(final float perSpeciesKeepRatio,
246 final BiPredicate<Individual<U>, Individual<U>> speciesPredicate, final SelectionPolicy speciesSelection) {
247 return new Builder<U>().perSpeciesKeepRatio(perSpeciesKeepRatio)
248 .speciesPredicate(speciesPredicate)
249 .speciesSelection(speciesSelection)
250 .build();
251 }
252
253 /**
254 * Creates a NEAT selection policy with default keep ratio and specified parameters.
255 *
256 * @param <U> the fitness value type
257 * @param speciesPredicate predicate for determining species membership
258 * @param speciesSelection selection policy for within-species parent selection
259 * @return a new NEAT selection policy with default keep ratio (0.9)
260 */
261 public static <U extends Comparable<U>> NeatSelection<U> of(
262 final BiPredicate<Individual<U>, Individual<U>> speciesPredicate, final SelectionPolicy speciesSelection) {
263 return new Builder<U>().speciesPredicate(speciesPredicate).speciesSelection(speciesSelection).build();
264 }
265
266 /**
267 * Creates a NEAT selection policy with standard default parameters.
268 *
269 * <p>Default configuration:
270 * <ul>
271 * <li><strong>Keep ratio</strong>: 0.9 (preserve top 90% of each species)</li>
272 * <li><strong>Minimum species size</strong>: 5 individuals</li>
273 * <li><strong>Compatibility distance</strong>: Threshold of 1.0 with standard coefficients</li>
274 * <li><strong>Species selection</strong>: Tournament selection with size 3</li>
275 * </ul>
276 *
277 * <p>Compatibility distance uses:
278 * <ul>
279 * <li>Weight coefficient: 1.0</li>
280 * <li>Excess gene coefficient: 2.0</li>
281 * <li>Disjoint gene coefficient: 2.0</li>
282 * <li>Distance threshold: 1.0</li>
283 * </ul>
284 *
285 * @param <U> the fitness value type
286 * @return a new NEAT selection policy with standard default parameters
287 */
288 public static <U extends Comparable<U>> NeatSelection<U> ofDefault() {
289 return new Builder<U>()
290 .speciesPredicate(
291 (i1, i2) -> NeatUtils.compatibilityDistance(i1.genotype(), i2.genotype(), 0, 2, 2, 1f) < 1.0)
292 .speciesSelection(Tournament.of(3))
293 .build();
294 }
295
296 /**
297 * Creates a NEAT selection policy using compatibility distance for speciation with default coefficients.
298 *
299 * <p>Default coefficients: excess=2.0, disjoint=2.0, weight difference=1.0
300 *
301 * @param <U> the fitness value type
302 * @param compatibilityThreshold maximum compatibility distance for same-species membership
303 * @param speciesSelection selection policy for within-species parent selection
304 * @return a new NEAT selection policy with default keep ratio and coefficients
305 */
306 public static <U extends Comparable<U>> NeatSelection<U> ofCompatibilityDistance(final float compatibilityThreshold,
307 final SelectionPolicy speciesSelection) {
308 Validate.isTrue(compatibilityThreshold > 0.0f, "Compatibility threshold must be positive");
309 return new Builder<U>()
310 .speciesPredicate(
311 (i1, i2) -> NeatUtils
312 .compatibilityDistance(i1.genotype(), i2.genotype(), 0, 2, 2, 1f) < compatibilityThreshold)
313 .speciesSelection(speciesSelection)
314 .build();
315 }
316
317 /**
318 * Creates a NEAT selection policy using compatibility distance for speciation with custom coefficients.
319 *
320 * @param <U> the fitness value type
321 * @param compatibilityThreshold maximum compatibility distance for same-species membership
322 * @param c1 excess gene coefficient
323 * @param c2 disjoint gene coefficient
324 * @param c3 weight difference coefficient
325 * @param speciesSelection selection policy for within-species parent selection
326 * @return a new NEAT selection policy with default keep ratio and custom coefficients
327 */
328 public static <U extends Comparable<U>> NeatSelection<U> ofCompatibilityDistance(final float compatibilityThreshold,
329 final float c1, final float c2, final float c3, final SelectionPolicy speciesSelection) {
330 Validate.isTrue(compatibilityThreshold > 0.0f, "Compatibility threshold must be positive");
331 return new Builder<U>()
332 .speciesPredicate(
333 (i1, i2) -> NeatUtils
334 .compatibilityDistance(i1.genotype(), i2.genotype(), 0, c1, c2, c3) < compatibilityThreshold)
335 .speciesSelection(speciesSelection)
336 .build();
337 }
338
339 /**
340 * Creates a NEAT selection policy using compatibility distance for speciation with custom keep ratio and
341 * coefficients.
342 *
343 * @param <U> the fitness value type
344 * @param perSpeciesKeepRatio proportion of each species to preserve (0.0 < ratio <= 1.0)
345 * @param compatibilityThreshold maximum compatibility distance for same-species membership
346 * @param c1 excess gene coefficient
347 * @param c2 disjoint gene coefficient
348 * @param c3 weight difference coefficient
349 * @param speciesSelection selection policy for within-species parent selection
350 * @return a new NEAT selection policy with the specified parameters
351 */
352 public static <U extends Comparable<U>> NeatSelection<U> ofCompatibilityDistance(final float perSpeciesKeepRatio,
353 final float compatibilityThreshold, final float c1, final float c2, final float c3,
354 final SelectionPolicy speciesSelection) {
355 Validate.isTrue(compatibilityThreshold > 0.0f, "Compatibility threshold must be positive");
356 return new Builder<U>().perSpeciesKeepRatio(perSpeciesKeepRatio)
357 .speciesPredicate(
358 (i1, i2) -> NeatUtils
359 .compatibilityDistance(i1.genotype(), i2.genotype(), 0, c1, c2, c3) < compatibilityThreshold)
360 .speciesSelection(speciesSelection)
361 .build();
362 }
363 }