View Javadoc
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>&lt; 0.8</strong>: Aggressive selection pressure</li>
127 	 * <li><strong>&gt; 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>&lt; 3</strong>: Very permissive, allows small species to survive</li>
149 	 * <li><strong>&gt; 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 }