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.Function;
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.Population;
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<Function<Population<T>, Population<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();
};
}
}