Skip to content
Snippets Groups Projects
Commit 69c4c9df authored by Bernhard Johannes Berger's avatar Bernhard Johannes Berger
Browse files

Interfaced Genetic Programming from Jenetics #56

parent eefbcc1d
No related branches found
No related tags found
No related merge requests found
Pipeline #308832 passed
Showing
with 646 additions and 20 deletions
......@@ -192,7 +192,9 @@ public class LanguageHelper {
}
private Object convertToJava(final Object current, final Type type) {
if(type instanceof InstanceType) {
log.info("Converting " + current + " to " + type);
if((type instanceof InstanceType || type instanceof DataType) && current instanceof OrExpression) {
return evaluator.evaluate(current);
} else if(type instanceof LiteralType) {
return readExpression(current, type);
......
......@@ -25,6 +25,44 @@ public final class Requirements {
}
}
/**
* Requires the value to be {@code false}.
*
* @param value Value to check.
* @param message Error message.
* @throws IllegalArgumentException iff the passed {@code value} is {@code true}.
*/
public static void requireFalse(final boolean value, final String message) {
if(value) {
throw new IllegalArgumentException(message);
}
}
/**
* Requires the value to be {@code true}.
*
* @param value Value to check.
* @throws IllegalArgumentException iff the passed {@code value} is {@code false}.
*/
public static void requireTrue(final boolean value) {
if(!value) {
throw new IllegalArgumentException("Value expected to be false.");
}
}
/**
* Requires the value to be {@code true}.
*
* @param value Value to check.
* @param message Message to throw.
* @throws IllegalArgumentException iff the passed {@code value} is {@code false}.
*/
public static void requireTrue(final boolean value, final String message) {
if(!value) {
throw new IllegalArgumentException(message);
}
}
/**
* Requires the arrays to be present and of same size.
*
......
package de.evoal.core.main.fitness;
import de.evoal.core.api.optimisation.OptimisationFunction;
import de.evoal.core.api.properties.Properties;
import de.evoal.core.api.properties.PropertiesSpecification;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.Random;
@Dependent
@Named("de.evoal.core.optimisation.random-fitness")
public class RandomFitness implements OptimisationFunction {
@Inject
@Named("optimisation-space-specification")
private PropertiesSpecification specification;
@Override
public double[] evaluate(Properties candidate) {
double [] result = new double[specification.size()];
for(int i = 0; i < specification.size(); ++i) {
result[i] = new Random().nextDouble();
}
return result;
}
}
package de.evoal.core.main.optimisation;
import de.evoal.core.api.optimisation.OptimisationFunction;
import de.evoal.core.api.properties.Properties;
import javax.enterprise.context.Dependent;
import javax.inject.Named;
@Dependent
@Named("de.evoal.core.optimisation.unknown-function")
public class UnknownOptimisationFunction implements OptimisationFunction {
@Override
public double[] evaluate(Properties candidate) {
throw new RuntimeException("The unknown function is not valid for usage during optimisation, please specify a proper optimisation function.");
}
}
......@@ -84,6 +84,7 @@ module de.evoal.core.main {
opens de.evoal.core.main.constraints.constraint.strategies.fitness to weld.core.impl;
opens de.evoal.core.main.constraints.correlation to weld.core.impl;
opens de.evoal.core.main.constraints.deviation to weld.core.impl;
opens de.evoal.core.main.fitness to weld.core.impl;
opens de.evoal.core.main.initial to weld.core.impl;
opens de.evoal.core.main.language to weld.core.impl;
opens de.evoal.core.main.optimisation to weld.core.impl;
......
......@@ -231,6 +231,9 @@ module de.evoal.core.optimisation {
*/
target : array instance variable;
}
type 'random-fitness' extends 'fitness-function' {
}
type 'problem-function' extends 'fitness-function' {
}
......
package de.evoal.core.ea.api.codec.program;
import de.evoal.core.api.cdi.EvoalComponent;
import io.jenetics.prog.op.Op;
public interface Operation extends Op<Double>, EvoalComponent<Operation> {
}
package de.evoal.core.ea.api.codec.program;
import de.evoal.core.api.cdi.EvoalComponent;
import io.jenetics.prog.ProgramChromosome;
public interface TreeValidator<T> extends EvoalComponent<TreeValidator<T>> {
public boolean validate(final ProgramChromosome<T> program);
}
package de.evoal.core.ea.api.fitness;
import de.evoal.core.api.cdi.EvoalComponent;
import de.evoal.core.api.properties.Properties;
import java.util.function.Function;
public interface GoodnessOfFitFunction extends EvoalComponent<GoodnessOfFitFunction>, Function<Properties, Double> {
}
......@@ -15,6 +15,8 @@ import de.evoal.core.api.correlations.Correlations;
import de.evoal.languages.model.base.Instance;
import de.evoal.core.api.utils.LanguageHelper;
import io.jenetics.*;
import io.jenetics.ext.SingleNodeCrossover;
import io.jenetics.ext.TreeGene;
import io.jenetics.util.Mean;
import javax.enterprise.context.ApplicationScoped;
import lombok.extern.slf4j.Slf4j;
......@@ -58,6 +60,7 @@ public class AltererFactory {
case "partial-matched-alterer": return createPartiallyMatchedAlterer(config);
case "correlation-partial-matched-alterer": return createCorrelationPartiallyMatchedAlterer(config);
case "probability-mutator": return createMutator(config);
case "gaussian-mutator": return createGaussianMutator(config);
case "correlation-gaussian-mutator": return createGaussianCorrelationMutator(config);
case "swap-mutator": return createSwapMutator(config);
......@@ -74,12 +77,25 @@ public class AltererFactory {
case "correlation-single-point-crossover": return createCorrelationSinglePointCrossover(config);
case "uniform-crossover": return createUniformCrossover(config);
case "correlation-uniform-crossover": return createCorrelationUniformCrossover(config);
case "single-node-crossover": return (Alterer<G, OptimisationValue>) createSingleNodeCrossover(config);
}
return BeanFactory.createComponent(AltererComponent.class, config);
}
private <G extends TreeGene<?, G>> Alterer<G, OptimisationValue> createSingleNodeCrossover(Instance config) {
final Double probability = helper.lookup(config, "probability");
return new SingleNodeCrossover<>(probability);
}
private <G extends Gene<?, G>> Alterer<G, OptimisationValue> createMutator(final Instance config) {
final Double probability = helper.lookup(config, "probability");
return new Mutator<>(probability);
}
private <G extends Gene<?, G>> Alterer<G, OptimisationValue> createUniformCrossover(final Instance config) {
final Double crossoverProbability = helper.lookup(config, "crossover-probability");
final Double swapProbability = helper.lookup(config,"swap-probability");
......
......@@ -2,64 +2,246 @@ package de.evoal.core.ea.main.codec.program;
import de.evoal.core.api.cdi.BeanFactory;
import de.evoal.core.api.properties.Properties;
import de.evoal.core.api.properties.PropertiesSpecification;
import de.evoal.core.api.properties.PropertySpecification;
import de.evoal.core.api.utils.LanguageHelper;
import de.evoal.core.api.utils.Requirements;
import de.evoal.core.ea.api.codec.CustomCodec;
import de.evoal.core.ea.main.codec.vector.VectorGenotypeCodec;
import de.evoal.core.ea.main.codec.vector.chromosome.DynamicChromosome;
import de.evoal.core.ea.api.codec.program.Operation;
import de.evoal.core.ea.api.codec.program.TreeValidator;
import de.evoal.core.ea.main.codec.program.operations.GenotypeInformation;
import de.evoal.languages.model.base.Array;
import de.evoal.languages.model.base.Definition;
import de.evoal.languages.model.base.Instance;
import de.evoal.languages.model.ddl.DataDescription;
import de.evoal.languages.model.ddl.StructuredDataDescription;
import de.evoal.languages.model.dl.util.FQNProvider;
import io.jenetics.Gene;
import io.jenetics.Chromosome;
import io.jenetics.Genotype;
import io.jenetics.ext.util.Tree;
import io.jenetics.ext.util.TreeNode;
import io.jenetics.prog.ProgramChromosome;
import io.jenetics.prog.ProgramGene;
import io.jenetics.prog.op.Const;
import io.jenetics.prog.op.EphemeralConst;
import io.jenetics.prog.op.Op;
import io.jenetics.prog.op.Var;
import io.jenetics.util.Factory;
import io.jenetics.util.ISeq;
import io.jenetics.util.RandomRegistry;
import lombok.extern.slf4j.Slf4j;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Slf4j
public class ProgramGenotypeCodec<G extends Gene<?, G>> implements CustomCodec<G> {
public class ProgramGenotypeCodec implements CustomCodec<ProgramGene<Double>> {
@Inject
private LanguageHelper helper;
@Inject
private @Named("genotype-specification") PropertiesSpecification specification;
@Inject
private @Named("optimisation-space-specification") PropertiesSpecification optimisationSpace;
private List<DataDescription> genotypeSpec = Collections.emptyList();
private PropertiesSpecification mapping;
private PropertiesSpecification variablesSpecification;
private List<GenotypeInformation> information;
@Override
public ProgramGenotypeCodec<G> init(final Instance config) {
public ProgramGenotypeCodec init(final Instance config) {
log.info("Initialising program genotype codec");
final Object[] chromosomeConfigurations = helper.lookup(config, "chromosomes");
final FQNProvider provider = new FQNProvider();
/*
dynamicTemplates = Arrays.stream(chromosomeConfigurations)
Requirements.requireFalse(Arrays.stream(chromosomeConfigurations)
.map(Instance.class::cast)
.anyMatch(i -> !"de.evoal.core.ea.genetic-programming.program-chromosome".equals(provider.get(i))),
"Non program-chromosome are not allowed.");
genotypeSpec = Arrays.stream(chromosomeConfigurations)
.map(Instance.class::cast)
.filter(i -> "de.evoal.core.ea.genetic-programming.program-chromosome".equals(provider.get(i)))
.map(i -> helper.<DataDescription>lookup(i, "content"))
.collect(Collectors.toList());
Requirements.requireTrue(
genotypeSpec.stream()
.allMatch(StructuredDataDescription.class::isInstance),
"A program chromosome can only use structured data: data <name> of instance program;"
);
Requirements.requireTrue(
genotypeSpec.stream()
.map(StructuredDataDescription.class::cast)
.map(StructuredDataDescription::getType)
.allMatch(s -> "de.evoal.core.ea.genetic-programming.program".equals(provider.get(s))),
"A program chromosome can only use structured data: data <name> of instance program;"
);
mapping = PropertiesSpecification.builder()
.addDescriptions(genotypeSpec.stream())
.build();
Requirements.requireTrue(mapping.size() == chromosomeConfigurations.length, "Required to be of same size.");
information = IntStream.range(0, mapping.size())
.mapToObj(i -> {
final Instance configuration = (Instance) chromosomeConfigurations[i];
return toChromosome(configuration, i);
})
.collect(Collectors.toList());
return this;
}
private GenotypeInformation toChromosome(final Instance configuration, int genotypeIndex) {
// TODO lookup fails if no constants are given instead of using the default values.
final List<Const<Double>> constants = readConstants(helper.lookup(configuration, "constants"));
final PropertiesSpecification variablesSpecification = readVariablesSpecification(helper.lookup(configuration, "variables"));
final List<Var<Double>> vars = createVariables(variablesSpecification);
final List<Op<Double>> operations = readOperations(helper.lookup(configuration, "operations"));
final int depth = helper.lookup(configuration, "initial-depth");
final List<EphemeralConst<Double>> eConstants = new ArrayList<>();
final Object [] emphConfigurations = helper.lookup(configuration, "ephemeral-constants");
Arrays.stream(emphConfigurations)
.map(Instance.class::cast)
.filter(i -> "de.evoal.core.optimisation.program-chromosome".equals(provider.get(i)))
.map(i -> ProgramChromosome.of())
.forEach(i -> {
int count = helper.lookup(i, "count");
int lower = helper.lookup(i, "lower");
int upper = helper.lookup(i, "upper");
IntStream.range(0, count)
.mapToObj(o -> EphemeralConst.of(() -> ((Integer)RandomRegistry.random().nextInt(lower, upper)).doubleValue()))
.forEach(eConstants::add);
});
Object [] validatorConfiguration = helper.lookup(configuration, "validators");
List<TreeValidator> validators = Arrays.stream(validatorConfiguration)
.map(Instance.class::cast)
.map(i -> BeanFactory.createComponent(TreeValidator.class, i))
.collect(Collectors.toList());
Predicate<? super ProgramChromosome<Double>> validator = c -> validators.stream().allMatch(v -> v.validate( c));
ProgramChromosome<Double> chromosome = ProgramChromosome.of(depth,
validator,
ISeq.of(operations),
ISeq.concat(
ISeq.concat(ISeq.of(vars), ISeq.of(constants)),
ISeq.of(eConstants)));
return new GenotypeInformation(mapping.get(genotypeIndex),
chromosome,
depth,
ISeq.of(operations),
ISeq.concat(ISeq.concat(ISeq.of(vars), ISeq.of(constants)), ISeq.of(eConstants)),
variablesSpecification,
optimisationSpace.get(genotypeIndex),
genotypeIndex,
validator);
}
private List<Op<Double>> readOperations(final Object [] operations) {
return Arrays.stream(operations)
.map(Instance.class::cast)
.map(i -> BeanFactory.createComponent(Operation.class, i))
.map(c -> (Op<Double>)(Op<?>)c)
.collect(Collectors.toList());
}
private List<Var<Double>> createVariables(final PropertiesSpecification variablesSpecification) {
return variablesSpecification.getProperties()
.stream()
.map(p -> Var.<Double>of(p.name(), variablesSpecification.indexOf(p)))
.collect(Collectors.toList());
TODO
*/
return this;
}
private PropertiesSpecification readVariablesSpecification(final Object [] variables) {
return PropertiesSpecification.builder()
.add(Arrays.stream(variables).map(Definition.class::cast))
.build();
}
private <T> List<Const<Double>> readConstants(final Object [] constants) {
return Arrays.stream(constants)
.map(Instance.class::cast)
.map(constant -> {
final String name = helper.lookup(constant, "name");
final double value = helper.lookup(constant, "value");
log.info("Found constant {} with value {}.", name, value);
return Const.of(name, value);
})
.collect(Collectors.toList());
}
@Override
public Genotype<G> encode(final Properties p) {
return null;
public Genotype<ProgramGene<Double>> encode(final Properties p) {
return Genotype.of(
information.stream()
.map(info -> {
PropertySpecification spec = info.searchSpaceProperty();
Tree<Op<Double>, ProgramGene<Double>> tree = (Tree<Op<Double>, ProgramGene<Double>>)p.get(spec);
return ProgramChromosome.of(tree, info.validator(), info.operations(), info.terminals());
})
.collect(Collectors.toList())
);
}
@Override
public Factory<Genotype<G>> encoding() {
return null;
public Factory<Genotype<ProgramGene<Double>>> encoding() {
return Genotype.of(
information.stream()
.map(GenotypeInformation::chromosome)
.collect(Collectors.toList())
);
}
@Override
public Function<Genotype<G>, Properties> decoder() {
return null;
public Function<Genotype<ProgramGene<Double>>, Properties> decoder() {
return this::decodeGenotype;
}
private Properties decodeGenotype(final Genotype<ProgramGene<Double>> genotype) {
final Properties result = new Properties(specification);
IntStream.range(0, genotype.length())
.forEach(i -> {
final Chromosome<ProgramGene<Double>> chromosome = genotype.get(i);
final TreeNode<Op<Double>> tree = chromosome.gene().toTreeNode();
final GenotypeInformation info = information.get(i);
result.put(info.searchSpaceProperty(), tree);
});
return result;
}
@Produces
@Named("de.evoal.core.ea.optimisation.program-genotype")
@Named("de.evoal.core.ea.genetic-programming.program-genotype")
@ApplicationScoped
public static CustomCodec create() {
log.info("Creating program-based codec for optimisation problem.");
......
package de.evoal.core.ea.main.codec.program;
import de.evoal.core.api.languages.ExpressionEvaluator;
import de.evoal.core.api.utils.InitializationException;
import de.evoal.core.api.utils.LanguageHelper;
import de.evoal.core.ea.api.codec.CustomCodecDescriber;
import de.evoal.languages.model.base.Attribute;
import de.evoal.languages.model.base.Definition;
import de.evoal.languages.model.base.Instance;
import lombok.extern.slf4j.Slf4j;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.inject.Named;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Dependent
@Named("de.evoal.core.ea.genetic-programming.program-genotype-describer")
@Slf4j
public class ProgramGenotypeDescriber implements CustomCodecDescriber {
private Instance configuration;
@Inject
private LanguageHelper helper;
@Inject
private ExpressionEvaluator evaluator;
@Override
public CustomCodecDescriber init(final Instance configuration) throws InitializationException {
log.info("Setting up describer");
this.configuration = configuration;
return CustomCodecDescriber.super.init(configuration);
}
@Override
public List<Definition> describe() {
log.info("Describing Genotype.");
final Object [] genes = helper.lookup(configuration, "chromosomes");
return Arrays.stream(genes)
.map(Instance.class::cast)
.map(i -> evaluator.attributeToObject(i, "content"))
.map(Definition.class::cast)
.collect(Collectors.toList());
}
}
package de.evoal.core.ea.main.codec.program.operations;
import de.evoal.core.ea.api.codec.program.Operation;
import javax.enterprise.context.Dependent;
import javax.inject.Named;
@Dependent
@Named("de.evoal.core.ea.genetic-programming.divide")
public class DivideOperation implements Operation {
@Override
public String name() {
return "divide";
}
@Override
public int arity() {
return 2;
}
@Override
public Double apply(Double [] o) {
return o[0] / o[1];
}
@Override
public String toString() {
return "divide";
}
}
package de.evoal.core.ea.main.codec.program.operations;
import de.evoal.core.api.properties.PropertiesSpecification;
import de.evoal.core.api.properties.PropertySpecification;
import io.jenetics.prog.ProgramChromosome;
import io.jenetics.prog.op.Op;
import io.jenetics.util.ISeq;
import java.util.function.Predicate;
/**
* Genotype information that stores information for the ProgramGenotypeCodec.
* It helps to not recalculate necessary information every time.
*
* @param searchSpaceProperty The search space property that is represented
* by a {@link io.jenetics.prog.ProgramChromosome}.
* @param chromosome A created chromosome that can be used as a template for
* creating new (random) chromosomes.
* @param initialDepth The initial depth of trees when they are generated
* randomly.
* @param operations List of all internal nodes.
* @param terminals List of all terminal nodes
* @param variablesSpecification The properties specification for the
* evaluation of the function.
* @param optimisationSpaceProperty The property generated by the function.
* @param chromosomeIndex Index of the chromosome in the genotype.
*/
public record GenotypeInformation(PropertySpecification searchSpaceProperty,
ProgramChromosome<Double> chromosome,
int initialDepth,
ISeq<Op<Double>> operations,
ISeq<? extends Op<Double>> terminals,
PropertiesSpecification variablesSpecification,
PropertySpecification optimisationSpaceProperty,
int chromosomeIndex,
Predicate<? super ProgramChromosome<Double>> validator) {
}
package de.evoal.core.ea.main.codec.program.operations;
import de.evoal.core.ea.api.codec.program.Operation;
import javax.enterprise.context.Dependent;
import javax.inject.Named;
@Dependent
@Named("de.evoal.core.ea.genetic-programming.minus")
public class MinusOperation implements Operation {
@Override
public String name() {
return "minus";
}
@Override
public int arity() {
return 2;
}
@Override
public Double apply(Double [] o) {
return o[0] - o[1];
}
@Override
public String toString() {
return "minus";
}
}
package de.evoal.core.ea.main.codec.program.operations;
import de.evoal.core.ea.api.codec.program.Operation;
import javax.enterprise.context.Dependent;
import javax.inject.Named;
@Dependent
@Named("de.evoal.core.ea.genetic-programming.multiply")
public class MultiplyOperation implements Operation {
@Override
public String name() {
return "multiply";
}
@Override
public int arity() {
return 2;
}
@Override
public Double apply(Double [] o) {
return o[0] * o[1];
}
@Override
public String toString() {
return "multiply";
}
}
package de.evoal.core.ea.main.codec.program.operations;
import de.evoal.core.ea.api.codec.program.Operation;
import javax.enterprise.context.Dependent;
import javax.inject.Named;
@Dependent
@Named("de.evoal.core.ea.genetic-programming.plus")
public class PlusOperation implements Operation {
@Override
public String name() {
return "plus";
}
@Override
public int arity() {
return 2;
}
@Override
public Double apply(Double [] o) {
return o[0] + o[1];
}
@Override
public String toString() {
return "plus";
}
}
package de.evoal.core.ea.main.codec.program.operations;
import de.evoal.core.ea.api.codec.program.Operation;
import javax.enterprise.context.Dependent;
import javax.inject.Named;
@Dependent
@Named("de.evoal.core.ea.genetic-programming.pow")
public class PowOperation implements Operation {
@Override
public String name() {
return "pow";
}
@Override
public int arity() {
return 2;
}
@Override
public Double apply(Double [] o) {
return Math.pow(o[0], o[1]);
}
@Override
public String toString() {
return "pow";
}
}
package de.evoal.core.ea.main.codec.program.operations;
import de.evoal.core.ea.api.codec.program.Operation;
import javax.enterprise.context.Dependent;
import javax.inject.Named;
@Dependent
@Named("de.evoal.core.ea.genetic-programming.sqrt")
public class SqrtOperation implements Operation {
@Override
public String name() {
return "sqrt";
}
@Override
public int arity() {
return 1;
}
@Override
public Double apply(Double [] o) {
return Math.sqrt(o[0]);
}
@Override
public String toString() {
return "sqrt";
}
}
package de.evoal.core.ea.main.codec.program.validators;
import de.evoal.core.api.cdi.EvoalComponent;
import de.evoal.core.api.utils.InitializationException;
import de.evoal.core.api.utils.LanguageHelper;
import de.evoal.core.ea.api.codec.program.TreeValidator;
import de.evoal.languages.model.base.Instance;
import io.jenetics.ext.util.TreeNode;
import io.jenetics.prog.ProgramChromosome;
import io.jenetics.prog.ProgramGene;
import io.jenetics.prog.op.Const;
import io.jenetics.prog.op.Var;
import javax.enterprise.context.Dependent;
import javax.inject.Inject;
import javax.inject.Named;
@Dependent
@Named("de.evoal.core.ea.genetic-programming.must-use-variable")
public class MustUseVariableValidator implements TreeValidator {
@Inject
private LanguageHelper helper;
private int requiredCount;
@Override
public EvoalComponent init(Instance configuration) throws InitializationException {
requiredCount = helper.lookup(configuration, "count");
return TreeValidator.super.init(configuration);
}
@Override
public boolean validate(final ProgramChromosome program) {
return program.root()
.breadthFirstStream()
.map(e -> ((ProgramGene)e).toTreeNode().value())
.filter(Var.class::isInstance)
.count() >= requiredCount;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment