1 package net.bmahe.genetics4j.neat.selection;
2
3 import java.util.ArrayList;
4 import java.util.Comparator;
5 import java.util.List;
6 import java.util.Objects;
7 import java.util.random.RandomGenerator;
8 import java.util.stream.IntStream;
9
10 import org.apache.commons.lang3.Validate;
11 import org.apache.logging.log4j.LogManager;
12 import org.apache.logging.log4j.Logger;
13
14 import net.bmahe.genetics4j.core.Genotype;
15 import net.bmahe.genetics4j.core.Individual;
16 import net.bmahe.genetics4j.core.Population;
17 import net.bmahe.genetics4j.core.selection.Selector;
18 import net.bmahe.genetics4j.core.spec.AbstractEAConfiguration;
19 import net.bmahe.genetics4j.core.util.IndividualUtils;
20 import net.bmahe.genetics4j.neat.NeatUtils;
21 import net.bmahe.genetics4j.neat.Species;
22 import net.bmahe.genetics4j.neat.SpeciesIdGenerator;
23 import net.bmahe.genetics4j.neat.spec.selection.NeatSelection;
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121 public class NeatSelectorImpl<T extends Number & Comparable<T>> implements Selector<T> {
122 public static final Logger logger = LogManager.getLogger(NeatSelectorImpl.class);
123
124 private final RandomGenerator randomGenerator;
125 private final NeatSelection<T> neatSelection;
126 private final SpeciesIdGenerator speciesIdGenerator;
127 private final Selector<T> speciesSelector;
128
129 private List<Species<T>> previousSpecies;
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 public NeatSelectorImpl(final RandomGenerator _randomGenerator,
145 final NeatSelection<T> _neatSelection,
146 final SpeciesIdGenerator _speciesIdGenerator,
147 final Selector<T> _speciesSelector) {
148 Objects.requireNonNull(_randomGenerator);
149 Objects.requireNonNull(_neatSelection);
150 Objects.requireNonNull(_speciesIdGenerator);
151 Objects.requireNonNull(_speciesSelector);
152
153 this.randomGenerator = _randomGenerator;
154 this.neatSelection = _neatSelection;
155 this.speciesIdGenerator = _speciesIdGenerator;
156 this.speciesSelector = _speciesSelector;
157
158 this.previousSpecies = new ArrayList<>();
159 }
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 protected Species<T> trimSpecies(final Species<T> species, final Comparator<Individual<T>> comparator,
176 final int minSpeciesSize, final float perSpeciesKeepRatio) {
177 Objects.requireNonNull(species);
178
179 final List<Individual<T>> members = species.getMembers();
180 final float speciesSize = members.size();
181 final int numIndividualtoKeep = (int) Math.max(minSpeciesSize, speciesSize * perSpeciesKeepRatio);
182
183 if (logger.isDebugEnabled()) {
184 logger.debug(
185 "Species id: {}, size: {}, perSepciesKeepRatio: {}, we want to keep {} members - best fitness: {}",
186 species.getId(),
187 speciesSize,
188 perSpeciesKeepRatio,
189 numIndividualtoKeep,
190 members.stream().max(comparator).map(Individual::fitness));
191 }
192
193 final Species<T> trimmedSpecies = new Species<>(species.getId(), List.of());
194 if (numIndividualtoKeep > 0) {
195 final var selectedIndividuals = members.stream()
196 .sorted(comparator.reversed())
197 .limit(numIndividualtoKeep)
198 .toList();
199
200 trimmedSpecies.addAllMembers(selectedIndividuals);
201 }
202 return trimmedSpecies;
203
204 }
205
206
207
208
209
210
211
212
213
214
215
216
217
218 protected List<Species<T>> eliminateLowestPerformers(final AbstractEAConfiguration<T> eaConfiguration,
219 final List<Species<T>> allSpecies) {
220 Objects.requireNonNull(eaConfiguration);
221 Objects.requireNonNull(allSpecies);
222
223 final Comparator<Individual<T>> comparator = IndividualUtils.fitnessBasedComparator(eaConfiguration);
224
225 final float perSpeciesKeepRatio = neatSelection.perSpeciesKeepRatio();
226 logger.trace("Keeping only the best {} number of individuals per species", perSpeciesKeepRatio);
227
228 final int minSpeciesSize = neatSelection.minSpeciesSize();
229
230 return allSpecies.stream()
231 .map(species -> trimSpecies(species, comparator, minSpeciesSize, perSpeciesKeepRatio))
232 .filter(species -> species.getNumMembers() > 0)
233 .toList();
234 }
235
236 @Override
237 public Population<T> select(final AbstractEAConfiguration<T> eaConfiguration, final long generation,
238 final int numIndividuals, final List<Genotype> genotypes, final List<T> fitnessScore) {
239 Objects.requireNonNull(eaConfiguration);
240 Objects.requireNonNull(genotypes);
241 Objects.requireNonNull(fitnessScore);
242 Validate.isTrue(generation >= 0);
243 Validate.isTrue(numIndividuals > 0);
244 Validate.isTrue(genotypes.size() == fitnessScore.size());
245
246 final Population<T> population = Population.of(genotypes, fitnessScore);
247
248 final List<Species<T>> allSpecies = NeatUtils.speciate(
249 randomGenerator,
250 speciesIdGenerator,
251 previousSpecies,
252 population,
253 neatSelection.speciesPredicate());
254
255 logger.debug("Number of species found: {}", allSpecies.size());
256 logger.trace("Species: {}", allSpecies);
257
258
259
260
261 final var allTrimmedSpecies = eliminateLowestPerformers(eaConfiguration, allSpecies);
262 logger.debug("After trimming, we have {} species", allTrimmedSpecies.size());
263
264 previousSpecies = allTrimmedSpecies;
265 if (allTrimmedSpecies.isEmpty()) {
266 return Population.empty();
267 }
268
269
270
271
272
273
274 final double[] sumFitnesses = new double[allTrimmedSpecies.size()];
275 double totalSum = 0;
276 for (int i = 0; i < allTrimmedSpecies.size(); i++) {
277 final var species = allTrimmedSpecies.get(i);
278 sumFitnesses[i] = species.getMembers()
279 .stream()
280 .mapToDouble(individual -> individual.fitness().doubleValue())
281 .sum() / (float) species.getNumMembers();
282 totalSum += sumFitnesses[i];
283 }
284
285 final List<Integer> decreasingFitnessIndex = IntStream.range(0, sumFitnesses.length)
286 .boxed()
287 .sorted(Comparator.comparing(i -> sumFitnesses[(int) i]).reversed())
288 .toList();
289
290 final Population<T> selected = new Population<>();
291 final List<Integer> selectedSpeciesIds = new ArrayList<>();
292
293 final List<Population<T>> trimmedPopulations = allTrimmedSpecies.stream()
294 .map(species -> Population.of(species.getMembers()))
295 .toList();
296
297 final boolean strictSpeciation = neatSelection.interspeciesMatingRate() == 0.0f;
298
299 int i = 0;
300 while (selected.size() < numIndividuals && i < sumFitnesses.length) {
301 int speciesIndex = decreasingFitnessIndex.get(i);
302
303 int numIndividualSpecies = (int) (numIndividuals * sumFitnesses[speciesIndex] / totalSum);
304 if (numIndividualSpecies > numIndividuals - selected.size()) {
305 numIndividualSpecies = numIndividuals - selected.size();
306 }
307
308 if (numIndividualSpecies > 0) {
309 if (strictSpeciation && numIndividualSpecies > 1) {
310 numIndividualSpecies = numIndividualSpecies - (numIndividualSpecies % 2);
311 }
312
313 if (numIndividualSpecies > 0) {
314 final Population<T> speciesPopulation = trimmedPopulations.get(speciesIndex);
315 final int speciesId = allTrimmedSpecies.get(speciesIndex).getId();
316
317 logger.debug(
318 "sub selecting {} for index {} - species id: {}",
319 numIndividualSpecies,
320 speciesIndex,
321 speciesId);
322
323 final var selectedFromSpecies = speciesSelector.select(
324 eaConfiguration,
325 generation,
326 numIndividualSpecies,
327 speciesPopulation.getAllGenotypes(),
328 speciesPopulation.getAllFitnesses());
329
330 selected.addAll(selectedFromSpecies);
331 for (int j = 0; j < selectedFromSpecies.size(); j++) {
332 selectedSpeciesIds.add(speciesId);
333 }
334 }
335 }
336
337 i++;
338 }
339
340 if (selected.size() < numIndividuals) {
341 logger.debug(
342 "There are less selected individual [{}] than desired [{}]. Will include additional invididuals",
343 selected.size(),
344 numIndividuals);
345 final Population<T> speciesPopulation = trimmedPopulations.get(decreasingFitnessIndex.getFirst());
346 final int speciesId = allTrimmedSpecies.get(decreasingFitnessIndex.getFirst()).getId();
347
348 int remainder = numIndividuals - selected.size();
349 if (strictSpeciation && remainder > 1) {
350 remainder = remainder - (remainder % 2);
351 }
352
353 if (remainder > 0) {
354 final var additionalSelected = speciesSelector.select(
355 eaConfiguration,
356 generation,
357 remainder,
358 speciesPopulation.getAllGenotypes(),
359 speciesPopulation.getAllFitnesses());
360
361 selected.addAll(additionalSelected);
362 for (int j = 0; j < additionalSelected.size(); j++) {
363 selectedSpeciesIds.add(speciesId);
364 }
365 }
366 }
367
368 if (strictSpeciation == false && selected.size() >= 2) {
369 applyInterspeciesMating(selected, selectedSpeciesIds, neatSelection.interspeciesMatingRate());
370 }
371
372 return selected;
373 }
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388 private void applyInterspeciesMating(final Population<T> selected, final List<Integer> selectedSpeciesIds,
389 final float rate) {
390 final List<Genotype> genotypes = selected.getAllGenotypes();
391 final List<T> fitnesses = selected.getAllFitnesses();
392
393 for (int pairStart = 0; pairStart + 1 < selected.size(); pairStart += 2) {
394 if (randomGenerator.nextFloat() >= rate) {
395 continue;
396 }
397
398 final int firstSpeciesId = selectedSpeciesIds.get(pairStart);
399 int swapCandidate = randomGenerator.nextInt(selected.size());
400 int attempts = 0;
401 while (selectedSpeciesIds.get(swapCandidate) == firstSpeciesId && attempts < selected.size()) {
402 swapCandidate = randomGenerator.nextInt(selected.size());
403 attempts++;
404 }
405
406 if (selectedSpeciesIds.get(swapCandidate) == firstSpeciesId) {
407 continue;
408 }
409
410 final int secondIndex = pairStart + 1;
411 swap(genotypes, fitnesses, selectedSpeciesIds, secondIndex, swapCandidate);
412 }
413 }
414
415 private void swap(final List<Genotype> genotypes, final List<T> fitnesses, final List<Integer> speciesIds,
416 final int i, final int j) {
417 final Genotype tempGenotype = genotypes.get(i);
418 genotypes.set(i, genotypes.get(j));
419 genotypes.set(j, tempGenotype);
420
421 final T tempFitness = fitnesses.get(i);
422 fitnesses.set(i, fitnesses.get(j));
423 fitnesses.set(j, tempFitness);
424
425 final int tempSpeciesId = speciesIds.get(i);
426 speciesIds.set(i, speciesIds.get(j));
427 speciesIds.set(j, tempSpeciesId);
428 }
429 }