1 package net.bmahe.genetics4j.neat;
2
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.List;
6 import java.util.Objects;
7
8 import net.bmahe.genetics4j.core.Individual;
9
10 /**
11 * Represents a species in the NEAT (NeuroEvolution of Augmenting Topologies) algorithm.
12 *
13 * <p>A Species groups together genetically similar individuals in the population, enabling fitness sharing and
14 * diversity preservation in NEAT evolution. Species are formed based on genetic compatibility distance, allowing
15 * individuals with similar network topologies to compete within their own niche rather than with the entire population.
16 *
17 * <p>Key characteristics:
18 * <ul>
19 * <li><strong>Genetic similarity</strong>: Members share similar network topologies and connection patterns</li>
20 * <li><strong>Fitness sharing</strong>: Members compete primarily within their species for reproductive
21 * opportunities</li>
22 * <li><strong>Diversity preservation</strong>: Protects innovative topologies from being eliminated by established
23 * forms</li>
24 * <li><strong>Dynamic membership</strong>: Species composition changes as individuals evolve and compatibility
25 * shifts</li>
26 * </ul>
27 *
28 * <p>NEAT speciation process:
29 * <ol>
30 * <li><strong>Compatibility measurement</strong>: Calculate genetic distance between individuals</li>
31 * <li><strong>Species assignment</strong>: Assign individuals to species based on distance thresholds</li>
32 * <li><strong>Representative selection</strong>: Choose species representatives for compatibility testing</li>
33 * <li><strong>Fitness sharing</strong>: Adjust individual fitness based on species membership size</li>
34 * <li><strong>Reproduction allocation</strong>: Allocate offspring based on species average fitness</li>
35 * </ol>
36 *
37 * <p>Species lifecycle management:
38 * <ul>
39 * <li><strong>Formation</strong>: New species created when individuals exceed compatibility threshold</li>
40 * <li><strong>Growth</strong>: Species gain members as similar individuals are assigned</li>
41 * <li><strong>Stagnation</strong>: Species may stagnate if they fail to improve over generations</li>
42 * <li><strong>Extinction</strong>: Species die out when they have no members or persistently poor performance</li>
43 * </ul>
44 *
45 * <p>Common usage patterns:
46 *
47 * <pre>{@code
48 * // Create new species with founding ancestors
49 * List<Individual<Double>> founders = List.of(individual1, individual2);
50 * Species<Double> species = new Species<>(42, founders);
51 *
52 * // Add members during population assignment
53 * species.addMember(similarIndividual1);
54 * species.addMember(similarIndividual2);
55 * species.addAllMembers(batchOfSimilarIndividuals);
56 *
57 * // Access species information
58 * int speciesId = species.getId();
59 * int memberCount = species.getNumMembers();
60 * List<Individual<Double>> allMembers = species.getMembers();
61 *
62 * // Species-based fitness sharing
63 * for (Individual<Double> member : species.getMembers()) {
64 * double sharedFitness = member.fitness() / species.getNumMembers();
65 * // Use shared fitness for selection
66 * }
67 * }</pre>
68 *
69 * <p>Ancestor tracking:
70 * <ul>
71 * <li><strong>Species representatives</strong>: Ancestors serve as compatibility test references</li>
72 * <li><strong>Historical continuity</strong>: Maintains connection to previous generations</li>
73 * <li><strong>Stability</strong>: Prevents species boundaries from shifting too rapidly</li>
74 * <li><strong>Representative selection</strong>: Best performers may become ancestors for next generation</li>
75 * </ul>
76 *
77 * <p>Fitness sharing mechanism:
78 * <ul>
79 * <li><strong>Within-species competition</strong>: Members primarily compete with each other</li>
80 * <li><strong>Diversity protection</strong>: Prevents single topology from dominating population</li>
81 * <li><strong>Innovation preservation</strong>: Allows new topologies time to optimize</li>
82 * <li><strong>Niche exploitation</strong>: Different species can specialize for different aspects of the problem</li>
83 * </ul>
84 *
85 * <p>Integration with NEAT selection:
86 * <ul>
87 * <li><strong>Speciation</strong>: Used by NeatSelectionPolicyHandler for population organization</li>
88 * <li><strong>Compatibility testing</strong>: Ancestors used as reference points for species assignment</li>
89 * <li><strong>Reproduction allocation</strong>: Species size influences offspring distribution</li>
90 * <li><strong>Population dynamics</strong>: Species creation, growth, and extinction drive population diversity</li>
91 * </ul>
92 *
93 * @param <T> the fitness value type (typically Double)
94 * @see NeatSelectionPolicyHandler
95 * @see SpeciesIdGenerator
96 * @see NeatUtils#computeCompatibilityDistance
97 * @see Individual
98 */
99 public class Species<T extends Comparable<T>> {
100
101 private final int id;
102 private final List<Individual<T>> ancestors = new ArrayList<>();
103 private final List<Individual<T>> members = new ArrayList<>();
104
105 /**
106 * Constructs a new species with the specified ID and founding ancestors.
107 *
108 * <p>The ancestors serve as reference points for compatibility testing and represent the genetic heritage of the
109 * species. New individuals are tested against these ancestors to determine species membership.
110 *
111 * @param _id unique identifier for this species
112 * @param _ancestors founding individuals that define the species genetic signature
113 * @throws IllegalArgumentException if ancestors is null
114 */
115 public Species(final int _id, final List<Individual<T>> _ancestors) {
116 Objects.requireNonNull(_ancestors);
117
118 this.id = _id;
119 ancestors.addAll(_ancestors);
120 }
121
122 /**
123 * Adds an individual as an ancestor of this species.
124 *
125 * <p>Ancestors serve as reference points for compatibility testing in subsequent generations. Typically, the best
126 * performers from a species may be promoted to ancestors to maintain species continuity.
127 *
128 * @param individual the individual to add as an ancestor
129 * @throws IllegalArgumentException if individual is null
130 */
131 public void addAncestor(final Individual<T> individual) {
132 Objects.requireNonNull(individual);
133
134 ancestors.add(individual);
135 }
136
137 /**
138 * Adds an individual as a member of this species.
139 *
140 * <p>Members are the current generation individuals that have been assigned to this species based on genetic
141 * compatibility. They participate in fitness sharing and species-based selection.
142 *
143 * @param individual the individual to add as a member
144 * @throws IllegalArgumentException if individual is null
145 */
146 public void addMember(final Individual<T> individual) {
147 Objects.requireNonNull(individual);
148 members.add(individual);
149 }
150
151 /**
152 * Adds multiple individuals as members of this species.
153 *
154 * <p>This is a convenience method for bulk assignment of compatible individuals to the species. All individuals in
155 * the collection will participate in fitness sharing within this species.
156 *
157 * @param individuals collection of individuals to add as members
158 * @throws IllegalArgumentException if individuals is null
159 */
160 public void addAllMembers(final Collection<Individual<T>> individuals) {
161 Objects.requireNonNull(individuals);
162
163 members.addAll(individuals);
164 }
165
166 /**
167 * Returns the number of ancestors in this species.
168 *
169 * <p>Ancestors serve as reference points for compatibility testing and represent the species genetic heritage from
170 * previous generations.
171 *
172 * @return the number of ancestors
173 */
174 public int getNumAncestors() {
175 return ancestors.size();
176 }
177
178 /**
179 * Returns the number of current members in this species.
180 *
181 * <p>The member count is used for fitness sharing calculations and reproduction allocation. Larger species will have
182 * their members' fitness values adjusted downward to prevent single species from dominating the population.
183 *
184 * @return the number of current members
185 */
186 public int getNumMembers() {
187 return members.size();
188 }
189
190 /**
191 * Returns the unique identifier for this species.
192 *
193 * <p>Species IDs are typically assigned by a SpeciesIdGenerator and remain constant throughout the species
194 * lifecycle.
195 *
196 * @return the species unique identifier
197 */
198 public int getId() {
199 return id;
200 }
201
202 /**
203 * Returns the list of ancestors for this species.
204 *
205 * <p>Ancestors are reference individuals used for compatibility testing when assigning new individuals to species.
206 * The returned list is mutable and modifications will affect the species behavior.
207 *
208 * @return mutable list of ancestor individuals
209 */
210 public List<Individual<T>> getAncestors() {
211 return ancestors;
212 }
213
214 /**
215 * Returns the list of current members in this species.
216 *
217 * <p>Members are the current generation individuals that participate in fitness sharing and species-based selection.
218 * The returned list is mutable and modifications will affect species membership.
219 *
220 * @return mutable list of member individuals
221 */
222 public List<Individual<T>> getMembers() {
223 return members;
224 }
225
226 @Override
227 public int hashCode() {
228 return Objects.hash(ancestors, id, members);
229 }
230
231 // Should it be id only?
232 @Override
233 public boolean equals(Object obj) {
234 if (this == obj) {
235 return true;
236 }
237 if (obj == null) {
238 return false;
239 }
240 if (getClass() != obj.getClass()) {
241 return false;
242 }
243 Species other = (Species) obj;
244 return Objects.equals(ancestors, other.ancestors) && id == other.id && Objects.equals(members, other.members);
245 }
246
247 @Override
248 public String toString() {
249 return "Species [id=" + id + ", ancestors=" + ancestors + ", members=" + members + "]";
250 }
251 }