View Javadoc
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 }