EASystem.java
package net.bmahe.genetics4j.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.Validate;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.bmahe.genetics4j.core.chromosomes.Chromosome;
import net.bmahe.genetics4j.core.chromosomes.factory.ChromosomeFactoryProvider;
import net.bmahe.genetics4j.core.combination.ChromosomeCombinator;
import net.bmahe.genetics4j.core.combination.GenotypeCombinator;
import net.bmahe.genetics4j.core.evaluation.FitnessEvaluator;
import net.bmahe.genetics4j.core.evolutionlisteners.EvolutionListener;
import net.bmahe.genetics4j.core.mutation.Mutator;
import net.bmahe.genetics4j.core.replacement.ReplacementStrategyImplementor;
import net.bmahe.genetics4j.core.selection.Selector;
import net.bmahe.genetics4j.core.spec.AbstractEAConfiguration;
import net.bmahe.genetics4j.core.spec.AbstractEAExecutionContext;
import net.bmahe.genetics4j.core.spec.EvolutionResult;
import net.bmahe.genetics4j.core.spec.ImmutableEvolutionResult;
import net.bmahe.genetics4j.core.termination.Termination;
import net.bmahe.genetics4j.core.util.GenotypeGenerator;
/**
* Main orchestrator class for evolutionary algorithms, managing the complete
* evolution process.
*
* <p>
* EASystem serves as the central coordinator that brings together all
* components of an evolutionary
* algorithm including genetic operators, selection strategies, evaluation
* functions, and termination
* criteria. It manages the evolutionary cycle and provides a unified interface
* for running optimizations.
*
* <p>
* The system coordinates the following evolutionary process:
* <ol>
* <li><strong>Initialization</strong>: Generate initial population of random
* genotypes</li>
* <li><strong>Evaluation</strong>: Compute fitness values for all
* individuals</li>
* <li><strong>Selection</strong>: Choose parents for reproduction based on
* fitness</li>
* <li><strong>Reproduction</strong>: Create offspring through crossover and
* mutation</li>
* <li><strong>Replacement</strong>: Integrate offspring into next
* generation</li>
* <li><strong>Termination check</strong>: Determine if stopping criteria are
* met</li>
* <li><strong>Iteration</strong>: Repeat until termination conditions are
* satisfied</li>
* </ol>
*
* <p>
* Key responsibilities include:
* <ul>
* <li><strong>Population management</strong>: Maintaining population size and
* diversity</li>
* <li><strong>Genetic operator coordination</strong>: Applying crossover,
* mutation, and selection</li>
* <li><strong>Fitness evaluation orchestration</strong>: Managing parallel and
* synchronous evaluation</li>
* <li><strong>Evolution monitoring</strong>: Providing hooks for logging and
* progress tracking</li>
* <li><strong>Resource management</strong>: Efficient memory usage and
* computation distribution</li>
* </ul>
*
* <p>
* Configuration components:
* <ul>
* <li><strong>EAConfiguration</strong>: Defines genetic representation and
* operator policies</li>
* <li><strong>EAExecutionContext</strong>: Provides runtime services and
* factory implementations</li>
* <li><strong>FitnessEvaluator</strong>: Computes quality measures for
* candidate solutions</li>
* <li><strong>Termination criteria</strong>: Determines when to stop
* evolution</li>
* </ul>
*
* <p>
* The system supports various evolutionary paradigms:
* <ul>
* <li><strong>Genetic Algorithms</strong>: Traditional binary and real-valued
* optimization</li>
* <li><strong>Genetic Programming</strong>: Evolution of tree-structured
* programs</li>
* <li><strong>Evolution Strategies</strong>: Real-valued optimization with
* adaptive parameters</li>
* <li><strong>Multi-objective optimization</strong>: Pareto-based optimization
* with multiple objectives</li>
* </ul>
*
* <p>
* Example usage:
*
* <pre>{@code
* // Configure the evolutionary algorithm
* EAConfiguration<Double> config = EAConfigurationBuilder.<Double>builder()
* .chromosomeSpecs(DoubleChromosomeSpec.of(10, -5.0, 5.0))
* .parentSelectionPolicy(Tournament.of(3))
* .mutationPolicy(RandomMutation.of(0.1))
* .build();
*
* // Set up execution context
* EAExecutionContext<Double> context = EAExecutionContexts.forScalarFitness();
*
* // Create fitness evaluator
* Fitness<Double> fitness = genotype -> {
* // Implement problem-specific fitness function
* return computeFitness(genotype);
* };
*
* // Build and run the evolutionary system
* EASystem<Double> system = EASystemFactory.from(config, context, fitness);
* EvolutionResult<Double> result = system.evolve(
* populationSize: 100,
* termination: Terminations.generations(1000)
* );
* }</pre>
*
* <p>
* Thread safety: EASystem instances are generally not thread-safe and should
* not be shared
* between multiple threads without external synchronization. However, the
* system supports
* parallel fitness evaluation internally when configured appropriately.
*
* @param <T> the type of fitness values, must be comparable for selection and
* ranking
* @see EASystemFactory
* @see net.bmahe.genetics4j.core.spec.EAConfiguration
* @see net.bmahe.genetics4j.core.spec.EAExecutionContext
* @see FitnessEvaluator
* @see Termination
*/
public class EASystem<T extends Comparable<T>> {
final static public Logger logger = LogManager.getLogger(EASystem.class);
private final GenotypeGenerator<T> genotypeGenerator;
private final FitnessEvaluator<T> fitnessEvaluator;
private final AbstractEAConfiguration<T> eaConfiguration;
private final AbstractEAExecutionContext<T> eaExecutionContext;
private final int populationSize;
private final List<ChromosomeCombinator<T>> chromosomeCombinators;
private final ChromosomeFactoryProvider chromosomeFactoryProvider;
private final ReplacementStrategyImplementor<T> replacementStrategyImplementor;
private final List<Mutator> mutators;
private final double offspringRatio;
private Selector<T> parentSelector;
public EASystem(final AbstractEAConfiguration<T> _eaConfiguration, final long _populationSize,
final List<ChromosomeCombinator<T>> _chromosomeCombinators, final double _offspringRatio,
final Selector<T> _parentSelectionPolicyHandler, final List<Mutator> _mutators,
final ReplacementStrategyImplementor<T> _replacementStrategyImplementor,
final AbstractEAExecutionContext<T> _eaExecutionContext, final FitnessEvaluator<T> _fitnessEvaluator) {
Objects.requireNonNull(_eaConfiguration);
Validate.isTrue(_populationSize > 0);
Objects.requireNonNull(_chromosomeCombinators);
Validate.isTrue(_chromosomeCombinators.size() == _eaConfiguration.numChromosomes());
Validate.inclusiveBetween(0.0, 1.0, _offspringRatio);
Objects.requireNonNull(_parentSelectionPolicyHandler);
Objects.requireNonNull(_replacementStrategyImplementor);
Objects.requireNonNull(_eaExecutionContext);
Objects.requireNonNull(_fitnessEvaluator);
this.eaConfiguration = _eaConfiguration;
this.eaExecutionContext = _eaExecutionContext;
this.populationSize = (int) _populationSize;
this.chromosomeCombinators = _chromosomeCombinators;
this.offspringRatio = _offspringRatio;
this.mutators = _mutators;
this.chromosomeFactoryProvider = _eaExecutionContext.chromosomeFactoryProvider();
this.fitnessEvaluator = _fitnessEvaluator;
parentSelector = _parentSelectionPolicyHandler;
this.replacementStrategyImplementor = _replacementStrategyImplementor;
this.genotypeGenerator = new GenotypeGenerator<>(chromosomeFactoryProvider, eaConfiguration);
}
private List<T> evaluate(final long generation, final List<Genotype> population) {
Validate.isTrue(generation >= 0);
Objects.requireNonNull(population);
Validate.isTrue(population.size() > 0);
logger.debug("Evaluating population of size {}", population.size());
final List<T> fitnesses = fitnessEvaluator.evaluate(generation, population);
logger.debug("Done evaluating population of size {}", population.size());
return fitnesses;
}
private List<Genotype> initializePopulation() {
final int initialPopulationSize = eaExecutionContext.populationSize();
logger.info("Generating initial population of {} individuals", initialPopulationSize);
final List<Genotype> genotypes = new ArrayList<>(initialPopulationSize);
final var seedPopulation = eaConfiguration.seedPopulation();
if (CollectionUtils.isNotEmpty(seedPopulation)) {
genotypes.addAll(seedPopulation);
}
if (genotypes.size() < initialPopulationSize) {
final var missingInitialIndividualCount = initialPopulationSize - genotypes.size();
logger.info(
"{} seed individual(s) added and generating {} individuals to reach the target of {} initial population size",
genotypes.size(),
missingInitialIndividualCount,
initialPopulationSize);
final var extraIndividuals = genotypeGenerator.generateGenotypes(missingInitialIndividualCount);
genotypes.addAll(extraIndividuals);
}
return genotypes;
}
private List<Genotype> mutateGenotypes(final List<Genotype> genotypes) {
Objects.requireNonNull(genotypes);
return genotypes.stream()
.map(child -> {
Genotype mutatedChild = child;
for (final Mutator mutator : mutators) {
mutatedChild = mutator.mutate(mutatedChild);
}
return mutatedChild;
})
.toList();
}
private List<Genotype> combineParents(final Population<T> parents, final GenotypeCombinator genotypeCombinator) {
Objects.requireNonNull(parents);
Objects.requireNonNull(genotypeCombinator);
final List<Genotype> children = new ArrayList<>();
int parentIndex = 0;
while (parentIndex + 1 < parents.size()) {
final Genotype firstParent = parents.getGenotype(parentIndex);
final T firstParentFitness = parents.getFitness(parentIndex);
final Genotype secondParent = parents.getGenotype(parentIndex + 1);
final T secondParentFitness = parents.getFitness(parentIndex + 1);
final List<List<Chromosome>> chromosomes = new ArrayList<>();
for (int chromosomeIndex = 0; chromosomeIndex < eaConfiguration.numChromosomes(); chromosomeIndex++) {
final Chromosome firstChromosome = firstParent.getChromosome(chromosomeIndex);
final Chromosome secondChromosome = secondParent.getChromosome(chromosomeIndex);
final List<Chromosome> combinedChromosomes = chromosomeCombinators.get(chromosomeIndex)
.combine(eaConfiguration, firstChromosome, firstParentFitness, secondChromosome, secondParentFitness);
chromosomes.add(combinedChromosomes);
logger.trace("Combining {} with {} ---> {}", firstChromosome, secondChromosome, combinedChromosomes);
}
final List<Genotype> offsprings = genotypeCombinator.combine(eaConfiguration, chromosomes);
children.addAll(offsprings);
parentIndex += 2;
}
return children;
}
/**
* Create offsprings without mutation
*
* @param population
* @param offspringsNeeded
* @return
*/
private List<Genotype> createBasicOffsprings(final Population<T> population, final int offspringsNeeded) {
Objects.requireNonNull(population);
Validate.isTrue(offspringsNeeded > 0);
final GenotypeCombinator genotypeCombinator = eaConfiguration.genotypeCombinator();
final int parentsNeeded = (int) (offspringsNeeded * 2);
logger.info("Selecting {} parents as we expect to generate {} offsprings", parentsNeeded, offspringsNeeded);
final Population<T> selectedParents = parentSelector
.select(eaConfiguration, parentsNeeded, population.getAllGenotypes(), population.getAllFitnesses());
logger.trace("Selected parents: {}", selectedParents);
Validate.isTrue(selectedParents.size() % 2 == 0);
logger.info("Combining parents into offsprings");
final List<Genotype> offsprings = combineParents(selectedParents, genotypeCombinator);
return offsprings;
}
/**
* Gets the evolutionary algorithm configuration used by this system.
*
* @return the EA configuration containing genetic operators, chromosome specs,
* and evolutionary parameters
*/
public AbstractEAConfiguration<T> getEAConfiguration() {
return eaConfiguration;
}
/**
* Gets the target population size for each generation.
*
* @return the population size as configured for this evolutionary system
*/
public long getPopulationSize() {
return populationSize;
}
/**
* Creates offspring from the given population through selection, crossover, and
* mutation.
*
* <p>
* This method orchestrates the reproductive process by:
* <ol>
* <li>Selecting parents from the population using the configured selection
* strategy</li>
* <li>Applying crossover operations to generate basic offspring</li>
* <li>Applying mutation operators to introduce genetic variation</li>
* </ol>
*
* @param population the current population to select parents from
* @param offspringsNeeded the number of offspring to generate
* @return a list of mutated offspring genotypes
* @throws NullPointerException if population is null
* @throws IllegalArgumentException if offspringsNeeded is not positive
*/
public List<Genotype> createOffsprings(final Population<T> population, final int offspringsNeeded) {
Objects.requireNonNull(population);
Validate.isTrue(offspringsNeeded > 0);
final List<Genotype> offpsrings = createBasicOffsprings(population, offspringsNeeded);
logger.info("Generated {} offsprings", offpsrings.size());
logger.info("Mutating offsprigns");
final List<Genotype> mutatedOffsprings = mutateGenotypes(offpsrings);
return mutatedOffsprings;
}
/**
* Executes the complete evolutionary algorithm process until termination
* criteria are met.
*
* <p>
* This is the main entry point for running evolution. The method manages the
* entire
* evolutionary cycle including:
* <ul>
* <li>Initial population generation and evaluation</li>
* <li>Iterative evolution through selection, reproduction, and replacement</li>
* <li>Progress monitoring via evolution listeners</li>
* <li>Termination condition checking</li>
* </ul>
*
* <p>
* The evolution process continues until the configured termination criteria
* (e.g., maximum generations, target fitness, convergence) are satisfied.
*
* @return an EvolutionResult containing the final population, fitness values,
* generation count, and configuration details
* @see Termination
* @see EvolutionResult
*/
public EvolutionResult<T> evolve() {
final Termination<T> termination = eaConfiguration.termination();
logger.info("Starting evolution");
fitnessEvaluator.preEvaluation();
long generation = 0;
final List<Genotype> genotypes = initializePopulation();
logger.info("Evaluating initial population");
final List<T> fitnessScore = evaluate(generation, genotypes);
Population<T> population = eaConfiguration.postEvaluationProcessor()
.map(pep -> pep.apply(Population.of(genotypes, fitnessScore)))
.orElseGet(() -> Population.of(genotypes, fitnessScore));
while (termination
.isDone(eaConfiguration, generation, population.getAllGenotypes(), population.getAllFitnesses()) == false) {
logger.info("Going through evolution of generation {}", generation);
for (final EvolutionListener<T> evolutionListener : eaExecutionContext.evolutionListeners()) {
evolutionListener
.onEvolution(generation, population.getAllGenotypes(), population.getAllFitnesses(), false);
}
final int offspringsNeeded = (int) (populationSize * offspringRatio);
final List<Genotype> offsprings = createOffsprings(population, offspringsNeeded);
logger.info("Evaluating offsprings");
final List<T> offspringScores = evaluate(generation, offsprings);
final Population<T> childrenPopulation = eaConfiguration.postEvaluationProcessor()
.map(pep -> pep.apply(Population.of(offsprings, offspringScores)))
.orElseGet(() -> Population.of(offsprings, offspringScores));
logger.info("Executing replacement strategy");
final int nextGenerationPopulationSize = eaExecutionContext.populationSize();
final Population<T> newPopulation = replacementStrategyImplementor.select(eaConfiguration,
nextGenerationPopulationSize,
population.getAllGenotypes(),
population.getAllFitnesses(),
childrenPopulation.getAllGenotypes(),
childrenPopulation.getAllFitnesses());
if (newPopulation.size() < nextGenerationPopulationSize) {
logger.info("New population only has {} members. Generating more individuals", newPopulation.size());
final List<Genotype> additionalIndividuals = genotypeGenerator
.generateGenotypes(nextGenerationPopulationSize - newPopulation.size());
logger.debug("Number of generated individuals: {}", additionalIndividuals.size());
if (additionalIndividuals.size() > 0) {
final List<T> additionalFitness = evaluate(generation, additionalIndividuals);
newPopulation.addAll(Population.of(additionalIndividuals, additionalFitness));
}
}
if (logger.isTraceEnabled()) {
logger.trace("[Generation {}] New population: {}", generation, Arrays.asList(newPopulation));
}
population = newPopulation;
generation++;
}
logger.info("Evolution has terminated");
// isDone has returned true and we want to let the evolutionListeners run a last
// time
for (final EvolutionListener<T> evolutionListener : eaExecutionContext.evolutionListeners()) {
evolutionListener.onEvolution(generation, population.getAllGenotypes(), population.getAllFitnesses(), true);
}
fitnessEvaluator.postEvaluation();
return ImmutableEvolutionResult
.of(eaConfiguration, generation, population.getAllGenotypes(), population.getAllFitnesses());
}
/**
* Evaluates a collection of genotypes once without running the full
* evolutionary process.
*
* <p>
* This method provides a way to evaluate genotypes using the configured fitness
* evaluator
* without executing the complete evolutionary algorithm. It handles the
* pre/post evaluation
* lifecycle and notifies evolution listeners of the results.
*
* <p>
* Useful for:
* <ul>
* <li>Testing fitness functions with specific genotypes</li>
* <li>Evaluating externally generated populations</li>
* <li>Batch evaluation scenarios</li>
* </ul>
*
* @param generation the generation number for logging and listener notification
* @param genotypes the list of genotypes to evaluate
* @return a list of fitness values corresponding to the input genotypes
* @throws IllegalArgumentException if generation is negative or genotypes is
* empty
* @throws NullPointerException if genotypes is null
*/
public List<T> evaluateOnce(final long generation, final List<Genotype> genotypes) {
Validate.isTrue(generation >= 0);
Objects.requireNonNull(genotypes);
Validate.isTrue(genotypes.size() > 0);
fitnessEvaluator.preEvaluation();
final var fitness = evaluate(generation, genotypes);
final var population = Population.of(genotypes, fitness);
for (final EvolutionListener<T> evolutionListener : eaExecutionContext.evolutionListeners()) {
evolutionListener.onEvolution(generation, population.getAllGenotypes(), population.getAllFitnesses(), true);
}
fitnessEvaluator.postEvaluation();
return fitness;
}
}