AbstractEAConfiguration.java
package net.bmahe.genetics4j.core.spec;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import org.apache.commons.lang3.Validate;
import org.immutables.value.Value;
import net.bmahe.genetics4j.core.Genotype;
import net.bmahe.genetics4j.core.combination.AllCasesGenotypeCombinator;
import net.bmahe.genetics4j.core.combination.GenotypeCombinator;
import net.bmahe.genetics4j.core.spec.chromosome.ChromosomeSpec;
import net.bmahe.genetics4j.core.spec.combination.CombinationPolicy;
import net.bmahe.genetics4j.core.spec.mutation.MutationPolicy;
import net.bmahe.genetics4j.core.spec.replacement.Elitism;
import net.bmahe.genetics4j.core.spec.replacement.ReplacementStrategy;
import net.bmahe.genetics4j.core.spec.selection.SelectionPolicy;
import net.bmahe.genetics4j.core.spec.selection.Tournament;
import net.bmahe.genetics4j.core.termination.Termination;
/**
* Evolutionary Algorithm Configuration.
* <p>This describe the set of strategies to use. They describe the genotype, the different policies for selection,
* combination as well as mutation, and other relevant parameters
* <p>Fitness computation is delegated to subclasses to better match the various ways in which they can be computed
*
* @param <T> Type of the fitness measurement
*/
public abstract class AbstractEAConfiguration<T extends Comparable<T>> {
/**
* Default offspring ratio
*/
public static final double DEFAULT_OFFSPRING_RATIO = 1.0;
/**
* Default optimization strategy
*/
public static final Optimization DEFAULT_OPTIMIZATION = Optimization.MAXIMIZE;
/**
* Genotype of the population
*
* @return
*/
public abstract List<ChromosomeSpec> chromosomeSpecs();
/**
* Defines the policy to select the parents. The selected parents will be used for generating the new offsprings
*
* @return
*/
public abstract SelectionPolicy parentSelectionPolicy();
/**
* Defines the policy to generate new offsprings from two parents
*
* @return
*/
public abstract CombinationPolicy combinationPolicy();
/**
* Defines what mutations to be performed on the offsprings
*
* @return
*/
public abstract List<MutationPolicy> mutationPolicies();
/**
* Defines the replacement strategy
* <p>The replacement strategy is what will determine the next population based on the generated and mutated
* offsprings along with the current population
* <p>If not specified, the default replacement strategy will be to use Elitism with tournament selection of 3
* individuals for both offsprings and survivors. The default offspring ratio is
* {@link Elitism#DEFAULT_OFFSPRING_RATIO}
*
* @return
*/
@Value.Default
public ReplacementStrategy replacementStrategy() {
final var replacementStrategyBuilder = Elitism.builder();
replacementStrategyBuilder.offspringRatio(Elitism.DEFAULT_OFFSPRING_RATIO)
.offspringSelectionPolicy(Tournament.of(3))
.survivorSelectionPolicy(Tournament.of(3));
return replacementStrategyBuilder.build();
}
/**
* Post-processing of a population after it got evaluated
* <p>This gives the opportunity to filter out, repair or rescore individuals
*
* @return Population to be used by the remaining evolution process
*/
public abstract Optional<PostEvaluationProcessor<T>> postEvaluationProcessor();
/**
* Defines termination condition
*
* @return
*/
public abstract Termination<T> termination();
/**
* Defines how to generate individuals
* <p>If not specified, the system will rely on the chromosome factories
*
* @return
*/
public abstract Optional<Supplier<Genotype>> genotypeGenerator();
/**
* Seed the initial population with specific individuals
*
* @return
*/
@Value.Default
public Collection<Genotype> seedPopulation() {
return Collections.emptyList();
}
/**
* Defines how to combine the offspring chromosomes generated
* <p>Combination of individuals is done on a per chromosome basis. This means some parents may generate a different
* number of children for each chromosome. This method will therefore define how to take all these generated
* chromosomes and combine them into offspring individuals
* <p>The current default implementation is to generate as many individual as there are combinations of generated
* chromosomes
*
* @return
*/
@Value.Default
public GenotypeCombinator genotypeCombinator() {
return new AllCasesGenotypeCombinator();
}
/**
* Defines how many children will be generated at each iteration. Value must be between 0 and 1 (inclusive) and
* represents a fraction of the population size
*
* @return
*/
@Value.Default
public double offspringGeneratedRatio() {
return DEFAULT_OFFSPRING_RATIO;
}
/**
* Defines the optimization goal, whether we want to maximize the fitness or minimize it
*
* @return
*/
@Value.Default
public Optimization optimization() {
return DEFAULT_OPTIMIZATION;
}
/**
* Validates the configuration
*/
@Value.Check
protected void check() {
Validate.isTrue(chromosomeSpecs().size() > 0, "No chromosomes were specified");
Validate.inclusiveBetween(0.0d, 1.0d, offspringGeneratedRatio());
}
/**
* Returns a specific chromosome spec from the genotype definition
*
* @param index
* @return
*/
public ChromosomeSpec getChromosomeSpec(final int index) {
Validate.isTrue(index >= 0);
Validate.isTrue(index < chromosomeSpecs().size());
return chromosomeSpecs().get(index);
}
/**
* Returns the currently number of chromosomes defined in the genotype
*
* @return
*/
public int numChromosomes() {
return chromosomeSpecs().size();
}
/**
* Return a comparator based on the optimization method and natural order
*
* @return
*/
public Comparator<T> fitnessComparator() {
return switch (optimization()) {
case MAXIMIZE -> Comparator.naturalOrder();
case MINIMIZE -> Comparator.reverseOrder();
};
}
}