Individual
We use the notion of individual, modeled in the Individual
record, to capture the genotype-phenotype representation. To this extent, we employ two generics parameters, G
and S
, to define the genotype and the phenotype spaces, respectively. In addition, since we also store the quality (or fitness) of the solution, i.e., of the phenotype encoded by the genotype, within the individual, we also add a generics parameter Q
.
public record Individual<G, S, Q>(
G genotype,
S solution,
Q fitness,
long fitnessMappingIteration,
long genotypeBirthIteration
)
Table of contents
Creation
To create an instance of Individual
, we need to
- obtain a genotype,
- map it to the corresponding phenotype, and
- evaluate the fitness of the candidate solution.
Note that an Individual
also stores the iteration at which the fitness is evaluated (fitnessMappingIteration
), and the iteration at which the genotype is obtained (genotypeBirthIteration
): these values model the “evolutionary age” for the individual in the evolutionary optimization run it belongs to.
A genotype can either be created from scratch or it can be the result of the application of genetic operators on pre-existing genotypes. In the first case, we employ the factory design pattern to build random genotypes.
@FunctionalInterface
public interface Factory<T> {
List<T> build(int n, RandomGenerator random);
}
We provide JGEA with some default implementations of factories for the most common genotypes, such as numeric genotypes, bit-strings, or trees.
Concerning genetic operators, we translate the concept into a general interface, extended by two more specific ones, accounting for the mutation and crossover operators.
public interface GeneticOperator<G> {
List<? extends G> apply(
List<? extends G> parents,
RandomGenerator random
);
int arity();
}
We remark that both methods deputy to computing the new genotype, build()
and apply()
, take an instance of RandomGenerator
to ensure reproducibility.
After obtaining a genotype, we map it to the corresponding phenotype with a simple Function<? super G, ? extends S>
, which can easily be defined on-the-fly, using Java lambda expressions. Last, we compute the fitness of the solution by invoking a Function<? super S, ? extends Q>
, such as the qualityFunction()
of a QualityBasedProblem
.
Selection
Several EAs require to select individuals for reproduction or survival. To model the selection process in JGEA we resort to the Selector
interface.
@FunctionalInterface
public interface Selector<T> {
<K extends T> K select(
PartiallyOrderedCollection<K> ks,
RandomGenerator random
);
}
We provide a few concrete selector implementations, such as the Tournament
, replicating the tournament selection, or the First
and Last
, returning the best or worst individual (or a random one among them, in case of fitness ties). Again, we remark the importance of passing an instance of RandomGenerator
to the select()
method for reproducibility concerns.