diff --git a/src/core/de.evoal.core.api/pom.xml b/src/core/de.evoal.core.api/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..0945a3481c98ccd5cb99382341e8aa3f03ebde5a --- /dev/null +++ b/src/core/de.evoal.core.api/pom.xml @@ -0,0 +1,100 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>de.evoal.core</groupId> + <artifactId>releng.parent</artifactId> + <version>0.9.0-SNAPSHOT</version> + + <relativePath>../de.evoal.core.releng.parent</relativePath> + </parent> + + <artifactId>core.api</artifactId> + <name>EvoAl - Core - API</name> + + <dependencies> + <!-- Include dependencies of EvoAl platform --> + <!-- CDI APIs --> + <dependency> + <groupId>jakarta.enterprise</groupId> + <artifactId>jakarta.enterprise.cdi-api</artifactId> + <scope>provided</scope> + </dependency> + + <!-- JSON API --> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>${jackson.version}</version> + </dependency> + + <!-- Logging API --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.deltaspike.core</groupId> + <artifactId>deltaspike-core-api</artifactId> + <version>${deltaspike.version}</version> + </dependency> + + <!-- TODO DO we want this dependency here? --> + <dependency> + <groupId>io.jenetics</groupId> + <artifactId>jenetics</artifactId> + <version>${jenetics.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.dl</artifactId> + <version>${evoal.languages.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.instance</artifactId> + <version>${evoal.languages.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.el</artifactId> + <version>${evoal.languages.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.common</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.ecore</artifactId> + <scope>provided</scope> + </dependency> + + + <!-- Math stuff --> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-math3</artifactId> + <version>3.6.1</version> + </dependency> + + <dependency> + <groupId>com.github.haifengl</groupId> + <artifactId>smile-math</artifactId> + <version>${smile.version}</version> + </dependency> + </dependencies> +</project> diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/board/Blackboard.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/board/Blackboard.java new file mode 100644 index 0000000000000000000000000000000000000000..0bf4d944e3e52a8d7fa8b77b849cd98c607fb867 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/board/Blackboard.java @@ -0,0 +1,111 @@ +package de.evoal.core.api.board; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Event; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Inject; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.Map; + +/** + * The blackboard is a large map of {@link BlackboardEntry} to arbitrary objects. + * For each entry change the blackboard will fire an event parametrized with the + * entry that was changed. The board is application-scoped to allow every one to + * access it if necessary. + */ +@Slf4j +@ApplicationScoped +public class Blackboard { + + /** + * The internal map. + */ + private final Map<String, Object> board = new HashMap<>(); + + /** + * Means to fire events if necessary. + */ + @Inject + private Event<BlackboardEntry> entryEvent; + + /** + * Setup a clean blackboard + */ + public Blackboard() { + board.put("EVALUATION", "none"); + } + + /** + * Fetches a value from the board. + * + * @param entry The element to fetch. + * @param <T> The element's type. + * @return The stored object. + * + * @throws IllegalStateException iff the entry is {@code null}. + */ + public <T> @NonNull T get(final BlackboardEntry entry) { + return get(entry.getLabel()); + } + + /** + * Fetches a value from the board. + * + * @param entry The element to fetch. + * @param <T> The element's type. + * @return The stored object. + * + * @throws IllegalStateException iff the entry is {@code null}. + */ + public <T> @NonNull T get(final String entry) { + final T value = (T) board.get(entry); + + if(value == null) { + throw new IllegalStateException("Value of " + entry + " is null."); + } + + return value; + } + + /** + * Binds an entry to a new value. A call to this function will trigger an + * CDI event with the passed {@code entry} to inform consumers of that + * entry. + * + * @param entry The entry to bind. + * @param element The element to bind. + */ + public void bind(final String entry, final Object element) { + log.info("Binding entry {} to {}.", entry, element); + + board.put(entry.toString(), element); + entryEvent.fire(BlackboardEntry.of(entry)); + } + + /** + * Parses all arguments from the passed command line arguments. + * + * @param args The command line arguments. + */ + public void readArguments(final String[] args) { + for(String arg : args) { + if(!arg.startsWith("-B")) { + continue; + } + + log.info("Setting blackboard entry from argument: '{}'.", arg); + + arg = arg.substring(2); + final int colonIndex = arg.indexOf('='); + + final String entry = arg.substring(0, colonIndex); + final String value = arg.substring(colonIndex + 1); + + bind(entry, value); + } + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/board/BlackboardEntry.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/board/BlackboardEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..fa81233b3d83a16ddd6bd55966f8803a601ab1a4 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/board/BlackboardEntry.java @@ -0,0 +1,100 @@ +package de.evoal.core.api.board; + +public final class BlackboardEntry { + + public static final String TARGET_PROPERTIES = "TARGET_PROPERTIES"; + public static final String TARGET_PROPERTIES_SOURCE = "TARGET_PROPERTIES_SOURCE"; + + /** + * Folder containing the constraint validation models. + */ + public static final String CONSTRAINT_VALIDATION_FOLDER = "CONSTRAINT_VALIDATION_FOLDER"; + + /** + * Chooses the evaluation kind. + */ + public static final String EVALUATION = "EVALUATION"; + + /** + * Number of evaluation runs. + */ + public static final String EVALUATION_ITERATIONS = "EVALUATION_ITERATIONS"; + + /** + * The evaluation run number. + */ + public static final String EVALUATION_RUN = "EVALUATION_RUN"; + + /** + * The actual output folder for the evaluation. + */ + public static final String EVALUATION_OUTPUT_FOLDER = "EVALUATION_OUTPUT_FOLDER"; + + /** + * Configuration file containing the configuration fot the standard fitness function. + */ + public static final String FITNESS_STANDARD_FUNCTION_FILE = "FITNESS_STANDARD_FUNCTION_FILE"; + + /** + * The heuristic configuration. + */ + public static final String EA_CONFIGURATION = "EA_CONFIGURATION"; + + /** + * The file containing the ea configuration. + */ + public static final String EA_CONFIGURATION_FILE = "EA_CONFIGURATION_FILE"; + + /** + * File containing the machine learning file. + */ + public static final String MACHINE_LEARNING_FILE = "MACHINE_LEARNING_FILE"; + + /** + * Name of the main to run. + */ + public static final String MAIN = "core:main"; + + /** + * The trained predictive function + */ + public static final String PREDICTIVE_FUNCTION = "PREDICTIVE_FUNCTION"; + + /** + * The predictive configuration to use + */ + public static final String PREDICTIVE_FUNCTION_CONFIGURATION = "PREDICTIVE_FUNCTION_CONFIGURATION"; + + /** + * File containing the predictive function file. + */ + public static final String PREDICTIVE_FUNCTION_FILE = "PREDICTIVE_FUNCTION_FILE"; + + /** + * Targets. + */ + public static final String TARGET_POINTS = "TARGET_POINTS"; + + /** + * File containing targets for evaluation. + */ + public static final String TARGETS_FILE = "TARGETS_FILE"; + + /** + * File containing the training points. + */ + public static final String TRAINING_POINT_FILE = "TRAINING_POINT_FILE"; + private final String label; + + private BlackboardEntry(final String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public static BlackboardEntry of(final String label) { + return new BlackboardEntry(label); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/board/package-info.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/board/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..33f1fc58b1c9504a52e4915fdba07565977b7435 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/board/package-info.java @@ -0,0 +1,4 @@ +/** + * The blackboard used to exchange data between different parts of the system. + */ +package de.evoal.core.api.board; \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/BeanFactory.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/BeanFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..f7b90f120d4dde3fbaa37e2c66aa561d22c9d80c --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/BeanFactory.java @@ -0,0 +1,63 @@ +package de.evoal.core.api.cdi; + +import de.evoal.core.api.utils.Requirements; +import lombok.extern.slf4j.Slf4j; +import org.apache.deltaspike.core.api.provider.BeanProvider; + +import javax.enterprise.inject.spi.Bean; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Deltaspike wrapper for reducing dependencies in extension. + */ +@Slf4j +public final class BeanFactory { + private BeanFactory() { + } + + public static <T> T create(final Class<T> type) { + Requirements.requireNotNull(type); + + log.info("Creating bean of type {}.", type); + try { + return BeanProvider.getContextualReference(type); + } catch(final IllegalStateException e) { + log.error("Failed to create contextual reference of type '{}'.", type); + final Set<Bean<T>> beans = BeanProvider.getBeanDefinitions(type, true, true); + + final String existingBeans = beans.stream().map(Bean::getName).collect(Collectors.joining(", ")); + log.error(" existing beans are: {}", existingBeans); + + throw e; + } + } + + /** + * Returns an instance of the given {@code type} and the given {@code name}. + * If necessary, the instance is created. + * + * @param name Name of the instance. + * @param type Type of the instance + * @param <T> The actual type + * @return A valid instance + */ + public static <T> T create(final String name, final Class<T> type) { + Requirements.requireNotNull(name); + Requirements.requireNotNull(type); + + log.info("Creating bean of type {} with name {}.", type, name); + + try { + return BeanProvider.getContextualReference(name, false, type); + } catch(final IllegalStateException e) { + log.error("Failed to create contextual reference of type '{}' with name '{}'.", type, name); + final Set<Bean<T>> beans = BeanProvider.getBeanDefinitions(type, true, true); + + final String existingBeans = beans.stream().map(Bean::getName).collect(Collectors.joining(", ")); + log.error(" existing beans are: {}", existingBeans); + + throw e; + } + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/BlackboardValue.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/BlackboardValue.java new file mode 100644 index 0000000000000000000000000000000000000000..4fe58e33e180663553e00501f0d860311cf78f33 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/BlackboardValue.java @@ -0,0 +1,22 @@ +package de.evoal.core.api.cdi; + +import de.evoal.core.api.board.BlackboardEntry; + +import javax.enterprise.util.Nonbinding; + +import javax.inject.Qualifier; +import java.lang.annotation.*; + +/** + * Annotation to let the framework inject an entry from the blackboard. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +public @interface BlackboardValue { + /** + * @return The entry to inject. + */ + public @Nonbinding String value(); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/ConfigurationValue.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/ConfigurationValue.java new file mode 100644 index 0000000000000000000000000000000000000000..38e2d7c927092e728e806723b291bdb4c7ea8985 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/ConfigurationValue.java @@ -0,0 +1,41 @@ +package de.evoal.core.api.cdi; + +import de.evoal.core.api.board.BlackboardEntry; + +import javax.enterprise.util.Nonbinding; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Annotation for injecting values from the configuration. You can annotate an + * attribute or a parameter in a CDI life-cycle method with this annotation + * to tell the framework that you want some information from a blackboard + * configuration: + * + * <pre> + * @Inject + * @ConfigurationValue(entry = BlackboardEntry.EA_CONFIGURATION, access = "algorithm.fitness.exponent") + * private double exponent; + * </pre> + * + * This example will load the ea configuration from the blackboard and search + * for the attribute {@code algorithm}. Afterwards, it looks up the attributes + * {@code fitness} and {@code exponent}. + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface ConfigurationValue { + /** + * @return The blackboard entry to use for the lookup. + */ + public String entry(); + + /** + * @return The access path. + */ + public @Nonbinding String access(); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/MainClass.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/MainClass.java new file mode 100644 index 0000000000000000000000000000000000000000..27cbbf65793326a5b31880b220fbdc3a841051e5 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/MainClass.java @@ -0,0 +1,13 @@ +package de.evoal.core.api.cdi; + +/** + * Base interface for all main classes. A main class must be annotated using the + * {@link javax.inject.Named} annotation, which can be used on the command line + * to select the main class to execute. + */ +public interface MainClass { + /** + * Run the main. + */ + public void run(); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/package-info.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f50f885eb9888dfdac798d743d57a98d97d78b58 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/cdi/package-info.java @@ -0,0 +1,4 @@ +/** + * All CDI-related functionality. + */ +package de.evoal.core.api.cdi; \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/codec/CustomCodec.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/codec/CustomCodec.java new file mode 100644 index 0000000000000000000000000000000000000000..fac1ddb75eb736abc32aaa157d3b98b5a5ce6491 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/codec/CustomCodec.java @@ -0,0 +1,21 @@ +package de.evoal.core.api.ea.codec; + +import de.evoal.core.api.properties.Properties; +import io.jenetics.Gene; +import io.jenetics.Genotype; +import io.jenetics.engine.Codec; + +/** + * A custom codec for our properties. + * + * @param <G> The gene type. + */ +public interface CustomCodec<G extends Gene<?, G>> extends Codec<Properties, G> { + /** + * Encodes an individual according to the codes into a genotype. + * + * @param p The individual to code. + * @return The generated (non-null) genotype. + */ + public Genotype<G> encode(final Properties p); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/Constraint.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/Constraint.java new file mode 100644 index 0000000000000000000000000000000000000000..02154607254620aecf3aba37ead9261d32497fe7 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/Constraint.java @@ -0,0 +1,52 @@ +package de.evoal.core.api.ea.constraints.model; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertySpecification; +import lombok.Data; + +import java.util.List; +import java.util.function.Function; + +/** + * A single evaluable constraint. A constraint ca be applied to an individual + * (properties) and produces a {@link ConstraintResult} in return. + */ +@Data +public class Constraint { + /** + * Type of the constraint + */ + private ConstraintType constraintType; + + /** + * Handling group for this constraint + */ + private String group; + + /** + * The applicable constraint function. + */ + private Function<Properties, Double> function; + + /** + * List of used properties. + */ + private List<PropertySpecification> usedProperties; + + /** + * Applies the constraint {@link #function} to the given individual {@code properties}. + * + * @param properties The individual to check. + * @return The result of the constraint's evaluation. + */ + public ConstraintResult apply(final Properties properties) { + final ConstraintResult result = new ConstraintResult(); + + result.setConstraint(this); + result.setComparisonDifference(function.apply(properties)); + result.getUsedProperties().addAll(usedProperties); + result.setType(getConstraintType()); + + return result; + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/ConstraintResult.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/ConstraintResult.java new file mode 100644 index 0000000000000000000000000000000000000000..af43503062aeec3e5fb0249957816daab9dd1477 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/ConstraintResult.java @@ -0,0 +1,33 @@ +package de.evoal.core.api.ea.constraints.model; + +import de.evoal.core.api.properties.PropertySpecification; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * The result of a constraint comparison. + */ +@Data +public class ConstraintResult { + /** + * The applied constraint. + */ + private Constraint constraint; + + /** + * Type of the constraint. + */ + private ConstraintType type; + + /** + * The calculated difference. + */ + private double comparisonDifference = 0.0; + + /** + * A list of used properties to calculate the constraint result. + */ + private final List<PropertySpecification> usedProperties = new ArrayList<>(); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/ConstraintType.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/ConstraintType.java new file mode 100644 index 0000000000000000000000000000000000000000..3ed3568a2d911de0ddeba80054377d8b4d1f31c8 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/ConstraintType.java @@ -0,0 +1,10 @@ +package de.evoal.core.api.ea.constraints.model; + +/** + * Type of the constraint. A constraint can either be an equality constraint + * or an inequality constraint. + */ +public enum ConstraintType { + Equality, + Inequality +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/Constraints.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/Constraints.java new file mode 100644 index 0000000000000000000000000000000000000000..9f33b621be70f558e9bd539b1c43afcd9d472974 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/Constraints.java @@ -0,0 +1,33 @@ +package de.evoal.core.api.ea.constraints.model; + +import de.evoal.core.api.properties.Properties; +import javax.enterprise.inject.Vetoed; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * A collection of constraints that are applicable to an individual. + */ +@Data @Vetoed +public class Constraints { + /** + * The actual list of constraints. + */ + private final List<Constraint> constraints = new ArrayList<>(); + + /** + * Applies the constraints to the given properties and produces the results. + * + * @param properties Properties to use + * @return A non-null list of constraint results. + */ + public List<ConstraintResult> apply(final Properties properties) { + return constraints.stream() + .map(c -> c.apply(properties)) + .collect(Collectors.toList()); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/package-info.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..31b616e2623cae00c5072441414b7a0b35d99250 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/model/package-info.java @@ -0,0 +1,4 @@ +/** + * The constraint model that can be used by implementing extensions. + */ +package de.evoal.core.api.ea.constraints.model; \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/package-info.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..77b586b44bffab64e59bd74a25afd22b9bd55dc2 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/package-info.java @@ -0,0 +1,5 @@ +/** + * This package contains everything that has to do with constraints of + * genotypes or individuals. + */ +package de.evoal.core.api.ea.constraints; \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/CalculationResult.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/CalculationResult.java new file mode 100644 index 0000000000000000000000000000000000000000..2c72effcd0d5a723b4fbebc0896b1918397559e9 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/CalculationResult.java @@ -0,0 +1,42 @@ +package de.evoal.core.api.ea.constraints.strategies; + +import de.evoal.core.api.ea.constraints.model.ConstraintResult; +import lombok.Data; + +/** + * The calculation result checks if the applied constraint was successful or + * not. + */ +@Data +public class CalculationResult { + /** + * The final result of the constraint evaluation. + */ + private final ConstraintResult result; + + /** + * If the evaluation is successful. + */ + private final boolean successful; + + public CalculationResult(final ConstraintResult result) { + this.result = result; + this.successful = isSuccessful(result); + } + + /** + * Was the application successful? + */ + public static boolean isSuccessful(final ConstraintResult result) { + switch (result.getType()) { + case Equality: + return result.getComparisonDifference() == 0.0; + + case Inequality: + return result.getComparisonDifference() > 0.0; + + default: + throw new IllegalStateException("Unhandled constraint type: " + result.getType()); + } + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/HandlingStrategy.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/HandlingStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..3a0687c18a99c947255ec7a360d0fbca6ca18145 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/HandlingStrategy.java @@ -0,0 +1,8 @@ +package de.evoal.core.api.ea.constraints.strategies; + +/** + * A constraint handling strategy. The actual implementation decides what to do with + * the {@link CalculationResult}. + */ +public interface HandlingStrategy { +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/MalusForFitnessStrategy.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/MalusForFitnessStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..8da60569df1af67c5502bb65bf7449563a044392 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/MalusForFitnessStrategy.java @@ -0,0 +1,47 @@ +package de.evoal.core.api.ea.constraints.strategies.fitness; + +import de.evoal.core.api.ea.constraints.strategies.HandlingStrategy; +import de.evoal.core.api.ea.constraints.strategies.fitness.internal.ChainFunction; +import de.evoal.core.api.ea.constraints.strategies.fitness.internal.IdentityFunction; +import de.evoal.core.api.properties.Properties; + +/** + * Class for applying the configured malus functions to given properties candidate. + */ +public class MalusForFitnessStrategy implements HandlingStrategy { + private final MalusFunction[] malusConversions; + + /** + * Constructor of the class. + * @param size Number of malus functions to apply by this strategy. + */ + public MalusForFitnessStrategy(final int size) { + malusConversions = new MalusFunction[size]; + + for(int index = 0; index < size; ++index) { + malusConversions[index] = new IdentityFunction(); + } + } + + /** + * Adds a {@code MalusFunction} for the given property index. + * + * @param propertyIndex Index of the property. + * @param malusFunction The {@code MalusFunction} to apply + */ + public void add(final int propertyIndex, final MalusFunction malusFunction) { + malusConversions[propertyIndex] = new ChainFunction(malusConversions[propertyIndex], malusFunction); + } + + /** + * Applies the malus functions. + * + * @param candidate The individual. + * @param fitnessValues The calculated fitness values. + */ + public void apply(final Properties candidate, double[] fitnessValues) { + for(int index = 0; index < fitnessValues.length; ++index) { + fitnessValues[index] = malusConversions[index].apply(candidate, fitnessValues[index]); + } + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/MalusFunction.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/MalusFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..d1d78bd6d8ecfad32450ef6cd2bbaec320a88a6b --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/MalusFunction.java @@ -0,0 +1,18 @@ +package de.evoal.core.api.ea.constraints.strategies.fitness; + +import de.evoal.core.api.properties.Properties; + +/** + * Base interface for a malus function. + */ +@FunctionalInterface +public interface MalusFunction { + /** + * Applies the malus function and calculates the adjusted fitness value. + * + * @param properties The individual. + * @param fitnessValue The current fitness value. + * @return The adapted fitness value. + */ + public double apply(final Properties properties, final double fitnessValue); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/internal/ChainFunction.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/internal/ChainFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..f16ef1f672f98499c62d30770dc3c329cfec4f29 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/internal/ChainFunction.java @@ -0,0 +1,21 @@ +package de.evoal.core.api.ea.constraints.strategies.fitness.internal; + +import de.evoal.core.api.ea.constraints.strategies.fitness.MalusFunction; +import de.evoal.core.api.properties.Properties; + +public class ChainFunction implements MalusFunction { + private final MalusFunction child; + private final MalusFunction strategy; + + public ChainFunction(final MalusFunction child, final MalusFunction strategy) { + this.child = child; + this.strategy = strategy; + } + + @Override + public double apply(final Properties properties, final double fitnessValue) { + double adaptedFitnessValue = child.apply(properties, fitnessValue); + + return strategy.apply(properties, adaptedFitnessValue); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/internal/IdentityFunction.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/internal/IdentityFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..8ca05256c3ef8af8855ce4c3beaabb38dad91b0c --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/internal/IdentityFunction.java @@ -0,0 +1,11 @@ +package de.evoal.core.api.ea.constraints.strategies.fitness.internal; + +import de.evoal.core.api.ea.constraints.strategies.fitness.MalusFunction; +import de.evoal.core.api.properties.Properties; + +public class IdentityFunction implements MalusFunction { + @Override + public double apply(final Properties properties, final double fitnessValue) { + return fitnessValue; + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/internal/package-info.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/internal/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..cc59a125f9fee7dc504421da26d3cd79c5246ceb --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/internal/package-info.java @@ -0,0 +1,4 @@ +/** + * Internal classes that are not inteded for usage by clients. + */ +package de.evoal.core.api.ea.constraints.strategies.fitness.internal; \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/package-info.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..9ead63b4869bf58610ae0ebfec29bbef32124180 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/constraints/strategies/fitness/package-info.java @@ -0,0 +1,7 @@ +/** + * The malus for fitness strategy applies a malus function to the fitness value + * that may take the constraint result into consideration. The general idea of + * this strategy is to reduce the fitness based on the calculated constraint + * difference. + */ +package de.evoal.core.api.ea.constraints.strategies.fitness; \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/FitnessBase.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/FitnessBase.java new file mode 100644 index 0000000000000000000000000000000000000000..1f562237f6bcd94889dfdecac12b538cb7b2fd8c --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/FitnessBase.java @@ -0,0 +1,57 @@ +package de.evoal.core.api.ea.fitness; + +import de.evoal.core.api.ea.constraints.strategies.fitness.MalusForFitnessStrategy; +import de.evoal.core.api.ea.fitness.type.FitnessConverter; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.BlackboardValue; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import de.evoal.core.api.properties.Properties; + +import javax.inject.Inject; +import javax.inject.Named; + +/** + * Base class for fitness functions that allows manipulating the calculated fitness + * values (in case of multi-optimization problems) based on, e.g., constraint + * violations. + */ +public abstract class FitnessBase implements FitnessEvaluator { + @Inject @Named("depending") + private FitnessConverter converter; + + @Inject + private MalusForFitnessStrategy malus; + + /** + * The surrogate function used. + */ + //@Inject + // TODO protected SurrogateFunction surrogate; + + /** + * The property values we are looking for. + */ + @Inject + @BlackboardValue(BlackboardEntry.TARGET_PROPERTIES) + protected Properties targetVector; + + /** + * The actual fitness function we are wrapping. + */ + protected abstract double [] _fitness(final Properties candidate); + + /** + * Calculates the fitness of the given candidate. + * + * @param candidate The possible candidate individual. + * @return The calculated fitness value. + */ + @Override + public final FitnessType evaluate(final Properties candidate) { + final double [] fitnessValues = _fitness(candidate); + + malus.apply(candidate, fitnessValues); + + return converter.convert(fitnessValues); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/FitnessEvaluator.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/FitnessEvaluator.java new file mode 100644 index 0000000000000000000000000000000000000000..1f234b10dd2204b70df24976048dada47290d95b --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/FitnessEvaluator.java @@ -0,0 +1,23 @@ +package de.evoal.core.api.ea.fitness; + +import de.evoal.core.api.ea.fitness.type.FitnessType; +import de.evoal.core.api.properties.Properties; + +import java.util.function.Function; + +/** + * A fitness evaluator calculates the fitness for a given individual. + */ +public interface FitnessEvaluator extends Function<Properties, FitnessType> { + /** + * Calculates the fitness value of the given individual. + */ + public FitnessType evaluate(final Properties individual); + + /** + * See {@link #evaluate(Properties)}. + */ + default FitnessType apply(final Properties individual) { + return evaluate(individual); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/package-info.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..0a81b12d924f5f6638ea7aaea5bc043899b451de --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/package-info.java @@ -0,0 +1,4 @@ +/** + * API package for fitness calculators. + */ +package de.evoal.core.api.ea.fitness; \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/type/FitnessConverter.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/type/FitnessConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..2cd6d599bf462c1b7a5ae292eb0b84996ffd4b97 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/type/FitnessConverter.java @@ -0,0 +1,14 @@ +package de.evoal.core.api.ea.fitness.type; + +/** + * Converter base for creating FitnessType's from distance arrays. + */ +public interface FitnessConverter { + /** + * Creates a fitness type instance for the given fitness values. + * + * @param fitnessValues The calculated fitness values to convert. + * @return The calculated FitnessType. + */ + public FitnessType convert(final double [] fitnessValues); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/type/FitnessType.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/type/FitnessType.java new file mode 100644 index 0000000000000000000000000000000000000000..d3e894bfa7ca2b1b891fedb79845d2ac18b7e0f4 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/fitness/type/FitnessType.java @@ -0,0 +1,11 @@ +package de.evoal.core.api.ea.fitness.type; + +/** + * The fitness type of the heuristic search. + */ +public interface FitnessType extends Comparable<FitnessType> { + /** + * @return All fitness values + */ + public double[] getFitnessValues(); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/package-info.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..a3ac75c03170a0588590326a18c540ea6b860a98 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/ea/package-info.java @@ -0,0 +1,4 @@ +/** + * Base package for all classes that are related to the evolutionary algorithm. + */ +package de.evoal.core.api.ea; \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/Properties.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/Properties.java new file mode 100644 index 0000000000000000000000000000000000000000..ad55de2c2e3995b4136bc6e4917cd2bc8d700131 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/Properties.java @@ -0,0 +1,139 @@ +package de.evoal.core.api.properties; + +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +public class Properties { + private final PropertiesSpecification specification; + + private final double[] values; + + public Properties(final PropertiesSpecification specification) { + this.specification = specification; + this.values = new double[specification.properties.size()]; + } + + public Properties(final PropertiesSpecification specification, final double[] data) { + this(specification); + + System.arraycopy(data, 0, this.values, 0, data.length); + } + + public Properties(final Properties other) { + this(other.specification, other.values); + } + + public static Properties create(final PropertiesSpecification spec, final Properties source) { + final Properties result = new Properties(spec); + + for(final PropertySpecification ps : spec.getProperties()) { + result.put(ps, source.get(ps)); + } + + return result; + } + + public double get(int i) { + return values[i]; + } + + public double get(final PropertySpecification spec) { + return values[specification.indexOf(spec)]; + } + + public PropertiesSpecification getSpecification() { + return specification; + } + + public double[] getValues() { + return values; + } + + public final double put(final PropertySpecification property, double d) { + final Integer index = specification.indices.get(property); + + if(index == null) { + throw new IllegalStateException("Property is not registered."); + } + + return put(index, d); + } + + public final double put(final int index, double d) { + return values[index] = d; + } + + public void set(final int index, final double value) { + values[index] = value; + } + + public int size() { + return values.length; + } + + public String toString() { + boolean notFirst = false; + + final StringBuilder builder = new StringBuilder(); + builder.append("<"); + + for(int i = 0; i < values.length; ++i) { + if(notFirst) { + builder.append(", "); + } else { + notFirst = true; + } + + builder.append(specification.properties.get(i)); + builder.append(" -> "); + builder.append(values[i]); + } + + builder.append(">"); + + return builder.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(values); + result = prime * result + Objects.hash(specification); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Properties other = (Properties) obj; + return Objects.equals(specification, other.specification) && Arrays.equals(values, other.values); + } + + public boolean contains(final PropertiesSpecification otherSpec) { + return specification.contains(otherSpec); + } + + public Properties putAll(final Properties properties) { + for(final PropertySpecification spec : getSpecification().getProperties()) { + put(spec, properties.get(spec)); + } + + return this; + } + + + public Properties putAll(final Map<String, Double> values) { + for(final PropertySpecification spec : getSpecification().getProperties()) { + put(spec, values.get(spec.name())); + } + + return this; + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/PropertiesPair.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/PropertiesPair.java new file mode 100644 index 0000000000000000000000000000000000000000..d7c4c641489ae197b41628cb9f2edd7b0b7154af --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/PropertiesPair.java @@ -0,0 +1,16 @@ +package de.evoal.core.api.properties; + +import org.apache.commons.math3.util.Pair; + +/** + * A pair of properties. + */ +public class PropertiesPair extends Pair<Properties, Properties> { + public PropertiesPair(Properties properties, Properties properties2) { + super(properties, properties2); + } + + public PropertiesPair(Pair<? extends Properties, ? extends Properties> entry) { + super(entry); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/PropertiesSpecification.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/PropertiesSpecification.java new file mode 100644 index 0000000000000000000000000000000000000000..3cd82ef459206dda885e79bad403ea36d2ef5a7c --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/PropertiesSpecification.java @@ -0,0 +1,117 @@ +package de.evoal.core.api.properties; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PropertiesSpecification { + public static class Builder { + private final Set<PropertySpecification> properties = new TreeSet<>(); + + public Builder() { + } + + /* + public Builder add(final PropertySpecification property) { + properties.add(property); + + return this; + } + + public Builder add(final String propertyName) { + add(new PropertySpecification(propertyName)); + + return this; + } + */ + + public Builder add(final Stream<String> names) { + names.forEach(n -> properties.add(new PropertySpecification(n))); + + return this; + } + + public Builder add(final PropertiesSpecification specification) { + properties.addAll(specification.getProperties()); + + return this; + } + + public PropertiesSpecification build() { + return new PropertiesSpecification(properties); + } + } + + Map<PropertySpecification, Integer> indices = new HashMap<>(); + + final List<PropertySpecification> properties; + + public PropertiesSpecification(final Collection<PropertySpecification> properties) { + this.properties = new ArrayList<>(properties); + + for(int i = 0; i < this.properties.size(); ++i) { + indices.put(this.properties.get(i), i); + } + } + + public PropertiesSpecification(final PropertySpecification property) { + properties = new ArrayList<>(); + properties.add(property); + + for(int i = 0; i < properties.size(); ++i) { + indices.put(properties.get(i), i); + } + } + + public PropertiesSpecification(final PropertiesSpecification other) { + this(other.properties); + } + + public static Builder builder() { + return new Builder(); + } + + public boolean contains(final PropertiesSpecification spec) { + return spec.properties + .stream() + .allMatch(indices::containsKey); + } + + public boolean contains(final PropertySpecification spec) { + return this.indices.containsKey(spec); + } + + public List<PropertySpecification> getProperties() { + return Collections.unmodifiableList(properties); + } + + public int indexOf(final PropertySpecification spec) { + return indices.get(spec); + } + + public int size() { + return properties.size(); + } + + public String toString() { + return "PropertiesSpecification [" + + properties + .stream() + .map(PropertySpecification::name) + .collect(Collectors.joining(",")) + + "]"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PropertiesSpecification that = (PropertiesSpecification) o; + return properties.equals(that.properties); + } + + @Override + public int hashCode() { + return Objects.hash(properties); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/PropertySpecification.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/PropertySpecification.java new file mode 100644 index 0000000000000000000000000000000000000000..f18a2c2c08a8e279dc513c25253dd2dc7771002c --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/PropertySpecification.java @@ -0,0 +1,33 @@ +package de.evoal.core.api.properties; + +import lombok.NonNull; + +import java.util.Objects; + +/** + * A property of the input or output domain is a simple label. + */ +public record PropertySpecification(@NonNull String name) implements Comparable { + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PropertySpecification other = (PropertySpecification) obj; + return Objects.equals(name, other.name); + } + + + @Override + public String toString() { + return "PropertySpecification [name=" + name + "]"; + } + + @Override + public int compareTo(final Object other) { + return name.compareTo(((PropertySpecification)other).name()); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/io/PropertiesReader.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/io/PropertiesReader.java new file mode 100644 index 0000000000000000000000000000000000000000..4ad4c7d44b34bb28a5db2bc330c7a6856b47e372 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/io/PropertiesReader.java @@ -0,0 +1,102 @@ +package de.evoal.core.api.properties.io; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.utils.Requirements; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; + +@Slf4j +public class PropertiesReader implements AutoCloseable, Iterator<Properties> { + + private final JsonParser jsonParser; + + public PropertiesReader(final File inputFile) throws IOException { + final ObjectMapper mapper = new ObjectMapper(); + jsonParser = mapper.createParser(inputFile); + jsonParser.nextToken(); + assertStartArray(); + } + + private Properties readProperties() throws IOException { + assertStartArray(); + + final Map<String, Double> entries = new TreeMap<>(); + while(!JsonToken.END_ARRAY.equals(jsonParser.currentToken())) { + Requirements.requireEqual(jsonParser.currentToken(), JsonToken.START_OBJECT); + + String name = null; + double value = 0.0; + + while(!JsonToken.END_OBJECT.equals(jsonParser.currentToken())) { + final String fieldName = jsonParser.nextFieldName(); + + if("name".equals(fieldName)) { + name = jsonParser.nextTextValue(); + } else if("value".equals(fieldName)) { + final JsonToken token = jsonParser.nextValue(); + assert(token == JsonToken.VALUE_NUMBER_FLOAT); + value = jsonParser.getDoubleValue(); + } + } + assertEndObject(); + + entries.put(name, value); + } + + assertEndArray(); + + final PropertiesSpecification spec = PropertiesSpecification.builder() + .add(entries.keySet().stream()) + .build(); + + return new Properties(spec).putAll(entries); + } + + private void assertEndArray() throws IOException { + Requirements.requireEqual(jsonParser.currentToken(), JsonToken.END_ARRAY); + jsonParser.nextToken(); + } + + + private void assertEndObject() throws IOException { + Requirements.requireEqual(jsonParser.currentToken(), JsonToken.END_OBJECT); + jsonParser.nextToken(); + } + + + private void assertStartArray() throws IOException { + Requirements.requireEqual(jsonParser.currentToken(), JsonToken.START_ARRAY); + jsonParser.nextToken(); + } + + private void assertStartObject() throws IOException { + Requirements.requireEqual(jsonParser.currentToken(), JsonToken.START_OBJECT); + jsonParser.nextToken(); + } + + @Override + public void close() throws Exception { + jsonParser.close(); + } + + @Override + public boolean hasNext() { + return !JsonToken.END_ARRAY.equals(jsonParser.currentToken()); + } + + @Override + @SneakyThrows(IOException.class) + public Properties next() { + return readProperties(); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/io/PropertiesWriter.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/io/PropertiesWriter.java index 54386ce3d46ce1fedde1e573b0d76c9ad4dbffa4..ce615e5be640652850f75d4455ddaf2c5f6c46e7 100644 --- a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/io/PropertiesWriter.java +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/io/PropertiesWriter.java @@ -1,9 +1,9 @@ -package de.evoal.core.api.model.io; +package de.evoal.core.api.properties.io; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; -import de.evoal.core.api.model.Properties; -import de.evoal.core.api.model.PropertySpecification; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertySpecification; import lombok.SneakyThrows; import java.io.File; diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/FileBasedPropertiesStreamSupplier.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/FileBasedPropertiesStreamSupplier.java new file mode 100644 index 0000000000000000000000000000000000000000..3a0e7b235a62ef884553f34ec380d2f041a4054a --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/FileBasedPropertiesStreamSupplier.java @@ -0,0 +1,34 @@ +package de.evoal.core.api.properties.stream; + +import de.evoal.core.api.properties.io.PropertiesReader; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.util.Collections; + +/** + * A simple {@see PropertiesFactory} that works on JSON-stored list of {@see Properties}. + */ +@Slf4j +public class FileBasedPropertiesStreamSupplier extends PropertiesBasedPropertiesStreamSupplier { + + /** + * Creates a new {@see PropertiesFactory} based on the given filename. + * + * @param filename The file to read. + */ + public FileBasedPropertiesStreamSupplier(final File filename) { + super(Collections.emptyList()); + log.info("Creating properties stream for {}.", filename); + + try(final PropertiesReader reader = new PropertiesReader(filename)) { + while(reader.hasNext()) { + properties.add(reader.next()); + } + } catch (final Exception e) { + log.error("Failed to read properties from {}.", filename, e); + } + + log.info("Successfully fetched {} properties from {}.", properties.size(), filename); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesBasedPropertiesPairStreamSupplier.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesBasedPropertiesPairStreamSupplier.java new file mode 100644 index 0000000000000000000000000000000000000000..bc3dd61f0592259a80c368ec96763a43cfac0fc0 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesBasedPropertiesPairStreamSupplier.java @@ -0,0 +1,44 @@ +package de.evoal.core.api.properties.stream; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesPair; +import de.evoal.core.api.properties.PropertiesSpecification; +import lombok.extern.slf4j.Slf4j; + +import java.util.stream.Stream; + +/** + * A supplier for a stream of training coordinates. + */ +@Slf4j +public class PropertiesBasedPropertiesPairStreamSupplier implements PropertiesPairStreamSupplier { + private final PropertiesStreamSupplier supplier; + private final PropertiesSpecification source; + private final PropertiesSpecification target; + + public PropertiesBasedPropertiesPairStreamSupplier(final PropertiesStreamSupplier supplier, final PropertiesSpecification source, final PropertiesSpecification target) { + this.supplier = supplier; + this.source = source; + this.target = target; + } + + @Override + public Stream<PropertiesPair> get() { + log.info("Creating properties pairs stream: {} -> {}", source, target); + final PropertiesSpecification merged = PropertiesSpecification + .builder() + .add(source) + .add(target) + .build(); + + return supplier.apply(merged) + .map(this::toPair); + } + + private PropertiesPair toPair(final Properties properties) { + return new PropertiesPair( + new Properties(source).putAll(properties), + new Properties(target).putAll(properties) + ); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesBasedPropertiesStreamSupplier.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesBasedPropertiesStreamSupplier.java new file mode 100644 index 0000000000000000000000000000000000000000..f69a76b988e84834d5093bda9df4b6e400a0e8d7 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesBasedPropertiesStreamSupplier.java @@ -0,0 +1,34 @@ +package de.evoal.core.api.properties.stream; + +import de.evoal.core.api.properties.Properties; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +/** + * A supplier of a stream of {@see Properties} based on a list of {@see Properties}. + */ +@Slf4j +public class PropertiesBasedPropertiesStreamSupplier implements PropertiesStreamSupplier { + /** + * The raw collection to stream + */ + protected final List<Properties> properties; + + /** + * Creates a new {@see PropertiesStreamSupplier} that is based on a collection of {@see Properties} and a required + * {@see PropertiesSpecification}. + * + * @param properties The collection of properties to stream. + */ + public PropertiesBasedPropertiesStreamSupplier(final List<Properties> properties) { + this.properties = new ArrayList<>(properties); + } + + @Override + public Stream<Properties> get() { + return properties.stream(); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesPairStreamSupplier.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesPairStreamSupplier.java new file mode 100644 index 0000000000000000000000000000000000000000..b597aef3d198990036dcd3fd9eae01e06b652ede --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesPairStreamSupplier.java @@ -0,0 +1,14 @@ +package de.evoal.core.api.properties.stream; + +import de.evoal.core.api.properties.PropertiesPair; +import de.evoal.core.api.properties.PropertiesSpecification; + +import java.util.function.BiFunction; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * A supplier of a stream of pairs of properties. Supplier can be reused. + */ +public interface PropertiesPairStreamSupplier extends Supplier<Stream<PropertiesPair>> { +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesStreamSupplier.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesStreamSupplier.java new file mode 100644 index 0000000000000000000000000000000000000000..448ecb623fcf76e39ae33c1508bf6aed225d3810 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/properties/stream/PropertiesStreamSupplier.java @@ -0,0 +1,18 @@ +package de.evoal.core.api.properties.stream; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; + +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * A supplier of a stream of properties. The supplier can be reused and can be asked for + * the properties conforming to a {@link PropertiesSpecification}. + */ +public interface PropertiesStreamSupplier extends Supplier<Stream<Properties>>, Function<PropertiesSpecification, Stream<Properties>> { + default Stream<Properties> apply(final PropertiesSpecification specification) { + return get().filter(p -> p.contains(specification)); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/Column.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/Column.java new file mode 100644 index 0000000000000000000000000000000000000000..8c88921717efc28f8472a009e519173f13ee0b24 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/Column.java @@ -0,0 +1,24 @@ +package de.evoal.core.api.statistics; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; + +/** + * Specification of a statistics column + */ +@AllArgsConstructor +@Data +public class Column { + /** + * Name of the column. + */ + @Getter + private String name; + + /** + * Type of the column. + */ + @Getter + private ColumnType type; +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/ColumnType.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/ColumnType.java new file mode 100644 index 0000000000000000000000000000000000000000..f1b8511d2e74aacb3b3946df22b93546add9c081 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/ColumnType.java @@ -0,0 +1,11 @@ +package de.evoal.core.api.statistics; + +/** + * Possible column types. + */ +public enum ColumnType { + Double, + Enum, + Integer, + String, +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/StatisticsWriter.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/StatisticsWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..29586f7637b9fe8f22706e068461424bd725949b --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/StatisticsWriter.java @@ -0,0 +1,33 @@ +package de.evoal.core.api.statistics; + +import de.evoal.core.api.ea.fitness.type.FitnessType; +import de.evoal.languages.model.instance.Instance; +import io.jenetics.engine.EvolutionResult; + +/** + * Evaluation-specific data writer for evaluation results. The information + * may be individual-, generation-, or run-specific and may be specific to + * a certain technique under investigation. What all writer have in common + * is the table-like structure of data (think of a CSV file). + * + * The framework will call {@link #init(Instance)} once before the evolutionary + * algorithm starts. While the EA runs, it will call {@link #add(EvolutionResult)} + * for each generation of individuals. When the EA terminated, the framework calls + * {@link #write()} once to force a serialization of the data. + */ +public interface StatisticsWriter { + /** + * Adds a evaluation result to the statistics. + */ + public void add(final EvolutionResult<?, FitnessType> result); + + /** + * Passes the writer configuration. + */ + public StatisticsWriter init(final Instance configuration); + + /** + * Write the statistics since the run is completed. + */ + public void write(); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/Writer.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/Writer.java new file mode 100644 index 0000000000000000000000000000000000000000..0a1c31895ab74ea4ca0015e9ff4880ce14f1c142 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/Writer.java @@ -0,0 +1,25 @@ +package de.evoal.core.api.statistics; + +/** + * Base interface for all statistics writer. + */ +public interface Writer { + + /** + * Writes a record. + * + * @param data An array containing all the data of the record (in the order specified). + * @throws WriterException Will be thrown if there is some problem while writing. + */ + void addRecord(final Object[] data) throws WriterException; + + /** + * Closes the writer. + */ + void close(); + + /** + * Flushes the contents to the disk. + */ + void flush(); +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/WriterContext.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/WriterContext.java new file mode 100644 index 0000000000000000000000000000000000000000..2b417785b384b1d78ff0526eef0a67c3e4241285 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/WriterContext.java @@ -0,0 +1,60 @@ +package de.evoal.core.api.statistics; + +import javax.enterprise.context.ApplicationScoped; +import java.util.*; + +/** + * Writer context for injecting columns into a writer. + */ +@ApplicationScoped +public class WriterContext { + /** + * List of columns to prepend to a newly created writer. + */ + protected List<Column> columns = new LinkedList<>(); + + /** + * Bindings of columns to their values. + */ + protected Map<Column, Object> values = new HashMap<>(); + + /** + * Adds a new column to the context. + * + * @param column The column to add. + */ + public void addColumn(final Column column) { + if(!columns.contains(column)) { + columns.add(column); + } + } + + /** + * Binds a column to a value. + * + * @param column The column to bind. + * @param value The value to bind. + */ + public void bindColumn(final Column column, final Object value) { + values.put(column, value); + } + + public int size() { + return columns.size(); + } + + /** + * @return A unmodifiable list of columns. + */ + public List<Column> getColumns() { + return Collections.unmodifiableList(columns); + } + + /** + * @param column The column to lookup. + * @return Returns the value for the column. + */ + public Object get(final Column column) { + return values.get(column); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/WriterException.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/WriterException.java new file mode 100644 index 0000000000000000000000000000000000000000..62f74d95c03867b8f5e29d9296ed5dd1ec1b639c --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/WriterException.java @@ -0,0 +1,10 @@ +package de.evoal.core.api.statistics; + +/** + * Exception for signaling errors while writing. + */ +public class WriterException extends Exception { + public WriterException(final String msg, final Throwable e) { + super(msg, e); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/WriterStrategy.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/WriterStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..0c6a46270cf99a10a27c71cd7a52d3ce2b2b1897 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/WriterStrategy.java @@ -0,0 +1,35 @@ +package de.evoal.core.api.statistics; + +import javax.inject.Inject; +import java.util.List; + +/** + * Base class for writer strategies (different serializing backends). A strategy + * can decide to reuse files, to store them in different formats, and so on. + */ +public abstract class WriterStrategy { + /** + * Context of the writer. + */ + @Inject + protected WriterContext context; + + /** + * Creates a writer for the given name and the given header. + * + * @param name Name of the output. + * @param header Header of the output. + * @return A non-null writer. + * + * @throws WriterException If there is some problem with creating the writer. + */ + public abstract Writer create(final String name, final List<Column> header) throws WriterException; + + /** + * Closes the writer. + * + * @param writer The writer to close. + * @throws WriterException when there is an error during closing. + */ + public abstract void close(Writer writer) throws WriterException; +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/package-info.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..87bd9222827c21571fdc8c6984face331dc6e4eb --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/statistics/package-info.java @@ -0,0 +1,6 @@ +/** + * This package contains all API functionality necessary to implement + * custom {@link de.evoal.core.api.statistics.StatisticsWriter} and to add + * custom {@link de.evoal.core.api.statistics.WriterStrategy}. + */ +package de.evoal.core.api.statistics; \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/utils/ConstantSwitch.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/utils/ConstantSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..7c7fcb5b7d47c13288c692f0c7c3d07341c0c310 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/utils/ConstantSwitch.java @@ -0,0 +1,129 @@ +package de.evoal.core.api.utils; + +import de.evoal.languages.model.el.*; +import de.evoal.languages.model.el.util.ELSwitch; + + +import java.util.Objects; + +public class ConstantSwitch extends ELSwitch<Object> { + private ConstantSwitch() { + } + + @Override + public Object caseOrExpression(final OrExpression object) { + Requirements.requireSize(object.getSubExpressions(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Object caseXorExpression(XorExpression object) { + Requirements.requireSize(object.getSubExpressions(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Object caseAndExpression(AndExpression object) { + Requirements.requireSize(object.getSubExpressions(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Object caseNotExpression(NotExpression object) { + return this.doSwitch(object.getOperand()); + } + + @Override + public Object caseComparisonExpression(ComparisonExpression object) { + Requirements.requireSize(object.getComparison(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Object caseAddOrSubtractExpression(final AddOrSubtractExpression object) { + Requirements.requireSize(object.getOperators(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Object caseMultiplyDivideModuloExpression(MultiplyDivideModuloExpression object) { + Requirements.requireSize(object.getOperators(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Object casePowerOfExpression(PowerOfExpression object) { + Requirements.requireNull(object.getRightOperand()); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Object caseUnaryAddOrSubtractExpression(UnaryAddOrSubtractExpression object) { + Requirements.requireSize(object.getOperators(), 0); + + Object Object = this.doSwitch(object.getSubExpression()); + + /* + for(final AddOrSubtractOperator aso : object.getOperators()) { + if(AddOrSubtractOperator.ADD.equals(aso)) { + continue; + } + + if(Object instanceof Integer) { + Object = Math.negateExact(Object.intValue()); + } else if(Object instanceof Double) { + Object = -Object.doubleValue(); + } + } + */ + + return Object; + } + /* + @Override + public Object caseCallOrLiteralOrReferenceOrParantheses(CallOrLiteralOrReferenceOrParantheses object) { + return super.caseCallOrLiteralOrReferenceOrParantheses(object); + } + */ + + @Override + public Object caseIntegerLiteral(final IntegerLiteral object) { + return object.getValue(); + } + + @Override + public Object caseDoubleLiteral(final DoubleLiteral object) { + return object.getValue(); + } + + @Override + public Object caseStringLiteral(StringLiteral object) { + return object.getValue(); + } + + @Override + public Object caseBooleanLiteral(BooleanLiteral object) { + return object.isValue(); + } + + @Override + public Object caseCall(final Call object) { + throw new IllegalStateException("Searching for Object but found a call."); + } + + @Override + public Object caseParantheses(Parantheses object) { + return this.doSwitch(object.getSubExpression()); + } + + public static Object findConstant(final Expression expression) { + return new ConstantSwitch().doSwitch(expression); + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/utils/LanguageHelper.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/utils/LanguageHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..513106d74eb9f2bce165271b1858d38048778d43 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/utils/LanguageHelper.java @@ -0,0 +1,86 @@ +package de.evoal.core.api.utils; + +import de.evoal.languages.model.el.*; +import de.evoal.languages.model.instance.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Helper class for processing eal files. + */ +public final class LanguageHelper { + /** + * Logger instance + */ + private static final Logger log = LoggerFactory.getLogger(LanguageHelper.class); + + /** + * Private constructor for avoiding instances of this class. + */ + private LanguageHelper() { + } + + public static <T> T lookup(final Instance instance, final String path) { + log.debug("Locking up '{}':", path); + + final String [] parts = path.split("\\."); + + Object current = instance; + + for(final String part : parts) { + try { + if(!(current instanceof Instance)) { + log.error("Failed to lookup part '{}' of path '{}'.", part, path); + throw new IllegalStateException("EA configuration is not valid."); + } + + boolean foundAttribute = false; + for(final Attribute attr : ((Instance)current).getAttributes()) { + final NameOrMisc nom = attr.getName(); + + if(nom instanceof Misc && ((Misc)nom).getName().equals(part)) { + current = attr.getValue(); + foundAttribute = true; + break; + } else if(nom instanceof Name && ((Name)nom).getName().getName().equals(part)) { + current = attr.getValue(); + foundAttribute = true; + break; + } + } + + if(!foundAttribute && "name".equals(part)) { + current = ((Instance)current).getName().getName(); + foundAttribute = true; + } + + if(!foundAttribute) { + log.error("Failed to lookup part '{}' of path '{}'. Returning null.", part, path); + return null; + } + } catch(final NullPointerException e) { + log.error("Failed to lookup part '{}' of path '{}'.", part, path); + throw e; + } + } + + if(current instanceof LiteralValue) { + Literal literal = ((LiteralValue)current).getLiteral(); + + if(literal instanceof StringLiteral) { + current = ((StringLiteral)literal).getValue(); + } else if(literal instanceof IntegerLiteral) { + current = ((IntegerLiteral)literal).getValue(); + } else if(literal instanceof DoubleLiteral) { + current = ((DoubleLiteral)literal).getValue(); + } else if(literal instanceof BooleanLiteral) { + current = ((BooleanLiteral)literal).isValue(); + } + } + + log.debug("Mapping " + path + " to " + current + " of type " + current.getClass()); + + return (T) current; + } + +} diff --git a/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/utils/Requirements.java b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/utils/Requirements.java new file mode 100644 index 0000000000000000000000000000000000000000..b608b1e5aca7bf7532c8eef2c98a86c6c3c971b5 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/de/evoal/core/api/utils/Requirements.java @@ -0,0 +1,225 @@ +package de.evoal.core.api.utils; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +/** + * Class for checking requirements of values, e.g., of parameters. + */ +public final class Requirements { + private Requirements() { + } + + /** + * Requires the value to be {@code false}. + * + * @param value Value to check. + * @throws IllegalArgumentException iff the passed {@code value} is {@code true}. + */ + public static void requireFalse(final boolean value) { + if(value) { + throw new IllegalArgumentException("Value expected to be false."); + } + } + + /** + * Requires the arrays to be present and of same size. + * + * @param a The first array. + * @param b The second array. + * + * @throws NullPointerException iff either {@code a} or {@code b} is {@code null}. + * @throws IllegalArgumentException iff the size of {@code a} and {@code b} differ. + */ + public static void requireSameSize(final double [] a, final double [] b) { + Objects.requireNonNull(a, "Array is not allowed to be null"); + Objects.requireNonNull(b, "Array is not allowed to be null"); + + if(a.length != b.length) { + throw new IllegalArgumentException("Arrays are expected to have the same size: " + a.length + " -- " + b.length); + } + } + + /** + * Requires the collections to be present and of same size. + * + * @param a The first collection. + * @param b The second collection. + * + * @throws NullPointerException iff either {@code a} or {@code b} is {@code null}. + * @throws IllegalArgumentException iff the size of {@code a} and {@code b} differ. + */ + public static void requireSameSize(final Collection<?> a, final Collection<?> b) { + Objects.requireNonNull(a, "Collection is not allowed to be null"); + Objects.requireNonNull(b, "Collection is not allowed to be null"); + + if(a.size() != b.size()) { + throw new IllegalArgumentException("Collections are expected to have the same size: " + a.size() + " -- " + b.size()); + } + } + + /** + * Requires the collection {@code collection} to be present and of size {@code size}. + * + * @param collection The collection to check. + * @param size The required size. + * + * @throws NullPointerException iff either {@code a} or {@code b} is {@code null}. + * @throws IllegalArgumentException iff the size of {@code a} and {@code b} differ. + */ + public static void requireSize(final Collection<?> collection, final int size) { + Objects.requireNonNull(collection, "Collection is not allowed to be null"); + + if(collection.size() != size) { + throw new IllegalArgumentException("Collections is expected to have size (" + size + "): " + collection.size()); + } + } + + /** + * Requires the collection {@code collection} to be present and of size {@code size}. + * + * @param collection The collection to check. + * @param size The required size. + * + * @throws NullPointerException iff either {@code a} or {@code b} is {@code null}. + * @throws IllegalArgumentException iff the size of {@code a} and {@code b} differ. + */ + public static void requireSizeGreaterThean(final Collection<?> collection, final int size) { + Objects.requireNonNull(collection, "Collection is not allowed to be null"); + + if(collection.size() <= size) { + throw new IllegalArgumentException("Collections expected size size (" + size + ") is not met: " + collection.size()); + } + } + + /** + * Requires the collection {@code collection} to be present but empty. + * + * @param collection The collection to check. + * + * @throws NullPointerException iff {@code collection} is {@code null}. + * @throws IllegalArgumentException iff the size of {@code collection} is greater than 0. + */ + public static void requireEmpty(final Collection<?> collection) { + Objects.requireNonNull(collection, "Collection is not allowed to be null"); + + if(!collection.isEmpty()) { + throw new IllegalArgumentException("Collections is expected to have size (0): " + collection.size()); + } + } + + /** + * Requires the parameters to be equal. + * + * @param a The first value to check. + * @param b The second value to check. + * + * @throws IllegalArgumentException iff the values are not equal. + */ + public static void requireEqual(final Object a, final Object b) { + if(!Objects.equals(a, b)) { + throw new IllegalArgumentException("Values are not equal: " + a + " != " + b); + } + } + /** + * Requires the parameters to have the same value. + * + * @param a The first value to check. + * @param b The second value to check. + * + * @throws IllegalArgumentException iff the values are not equal. + */ + public static void requireEqual(final long a, final long b) { + if(a != b) { + throw new IllegalArgumentException("Values are not equal: " + a + " != " + b); + } + } + + /** + * Requires the object to exist. + * + * @param object The object to check. + * + * @throws NullPointerException iff {@code object} is {@code null}. + */ + public static void requireNotNull(final Object object) { + Objects.requireNonNull(object, "Object is not allowed to be null"); + } + + /** + * Requires the object to be null. + * + * @param object The object to check. + * + * @throws IllegalArgumentException iff {@code object} is not {@code null}. + */ + public static void requireNull(final Object object) { + if(object != null) { + throw new IllegalArgumentException("Object is required to be null"); + } + } + + /** + * Requires {@code object} to be of type {@code type} + * + * @param object The object to check + * @param type The required type. + * + * @throws NullPointerException iff either {@code list} or {@code type} is {@code null}. + * @throws IllegalArgumentException iff the object is not of the required type. + */ + private static void requireType(final Object object, final Class<?> type) { + Objects.requireNonNull(type, "Type is not allowed to be null"); + + if(!type.isAssignableFrom(object.getClass())) { + throw new IllegalArgumentException("Object of type " + object.getClass() + " is not assignable to type " + type.getClass()); + } + } + + /** + * Requires the object in {@code list} at {@code index} to be of type {@code type}. + * + * @param list The hosting list. + * @param index The index of the element to check. + * @param type The required type. + * + * @throws NullPointerException iff either {@code list} or {@code type} is {@code null}. + * @throws IllegalArgumentException iff the object is not of the required type. + */ + public static void requireType(final List<Object> list, final int index, final Class<?> type) { + Objects.requireNonNull(list, "List is not allowed to be null"); + + requireType(list.get(index), type); + } + + /** + * Requires the passed value to be a probability. + * + * @param value The value to check. + * @return The probability itself for further usage. + * + * @throws IllegalArgumentException Iff the passed value is not a probability. + */ + public static double requireProbability(final double value) { + if(value < 0.0 || value > 0.0) { + throw new IllegalArgumentException("Passed value is not a probability: " + value); + } + + return value; + } + + /** + * Requires the value to be positive. + * + * @param value The value to check. + * @return {@code p} itself for further usage. + */ + public static double nonNegative(final double value) { + if(value < 0.0) { + throw new IllegalArgumentException("Passed value is negative: " + value); + } + + return value; + } +} diff --git a/src/core/de.evoal.core.api/src/main/java/module-info.java b/src/core/de.evoal.core.api/src/main/java/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..c41205f1c06e6322b823d2f7690a371b6b4a355c --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/java/module-info.java @@ -0,0 +1,38 @@ +open module de.evoal.core.api { + requires java.base; + + requires jakarta.enterprise.cdi.api; + requires jakarta.inject.api; + requires deltaspike.core.api; + + requires lombok; + + requires org.slf4j; + + requires io.jenetics.base; + requires commons.math3; + requires smile.math; + + requires com.fasterxml.jackson.databind; + + requires org.eclipse.emf.common; + requires org.eclipse.emf.ecore; + + requires de.evoal.languages.model.dl; + requires de.evoal.languages.model.el; + requires de.evoal.languages.model.instance; + + exports de.evoal.core.api.board; + exports de.evoal.core.api.cdi; + exports de.evoal.core.api.ea.codec; + exports de.evoal.core.api.ea.constraints.model; + exports de.evoal.core.api.ea.constraints.strategies; + exports de.evoal.core.api.ea.constraints.strategies.fitness; + exports de.evoal.core.api.ea.fitness; + exports de.evoal.core.api.ea.fitness.type; + exports de.evoal.core.api.properties; + exports de.evoal.core.api.properties.io; + exports de.evoal.core.api.properties.stream; + exports de.evoal.core.api.statistics; + exports de.evoal.core.api.utils; +} \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/resources/META-INF/MANIFEST.MF b/src/core/de.evoal.core.api/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000000000000000000000000000000..348f1bdd38a493271cf17f9cb618d65ca1ef08be --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1 @@ +Manifest-Version: 1.0 \ No newline at end of file diff --git a/src/core/de.evoal.core.api/src/main/resources/META-INF/beans.xml b/src/core/de.evoal.core.api/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000000000000000000000000000000..848dca3b29cc3f1f9879d2c86d586612e5d8b3e7 --- /dev/null +++ b/src/core/de.evoal.core.api/src/main/resources/META-INF/beans.xml @@ -0,0 +1,6 @@ +<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" + bean-discovery-mode="annotated" + version="2.0"> +</beans> \ No newline at end of file diff --git a/src/core/de.evoal.core.main/pom.xml b/src/core/de.evoal.core.main/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..57992e00fb4ddd828a37034807049236f0331f3c --- /dev/null +++ b/src/core/de.evoal.core.main/pom.xml @@ -0,0 +1,283 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>de.evoal.core</groupId> + <artifactId>releng.parent</artifactId> + <version>0.9.0-SNAPSHOT</version> + + <relativePath>../de.evoal.core.releng.parent</relativePath> + </parent> + + <artifactId>core.main</artifactId> + <name>EvoAl - Core - Main</name> + + <properties> + <javax.activation.version>1.2.0</javax.activation.version> + + <!-- DeltaSpike uses this version in its documentation. Newer version didn't work for some reasons. --> + <weld.version>3.1.9.Final</weld.version> + </properties> + + <dependencies> + <!-- Weld for CDI (@runtime) --> + <dependency> + <groupId>org.jboss.weld.se</groupId> + <artifactId>weld-se-core</artifactId> + <version>${weld.version}</version> + </dependency> + + <!-- Jandex results in a NPE at the moment --> + <dependency> + <groupId>org.jboss</groupId> + <artifactId>jandex</artifactId> + <version>2.4.1.Final</version> + <scope>runtime</scope> + </dependency> + + <!-- logback for logging (@runtime) --> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>1.4.0</version> + <scope>runtime</scope> + </dependency> + + <!-- deltaspike --> + <dependency> + <groupId>org.apache.deltaspike.core</groupId> + <artifactId>deltaspike-core-api</artifactId> + <version>${deltaspike.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.deltaspike.cdictrl</groupId> + <artifactId>deltaspike-cdictrl-api</artifactId> + <version>${deltaspike.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.deltaspike.cdictrl</groupId> + <artifactId>deltaspike-cdictrl-weld</artifactId> + <version>${deltaspike.version}</version> + </dependency> + + <dependency> + <groupId>org.apache.deltaspike.core</groupId> + <artifactId>deltaspike-core-impl</artifactId> + <version>${deltaspike.version}</version> + <scope>runtime</scope> + </dependency> + + <!-- Include dependencies of parent --> + <!-- CDI APIs --> + <dependency> + <groupId>jakarta.enterprise</groupId> + <artifactId>jakarta.enterprise.cdi-api</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>javax.annotation</groupId> + <artifactId>javax.annotation-api</artifactId> + <scope>runtime</scope> + </dependency> + + <!-- Logging API --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>compile</scope> + </dependency> + + <!-- CSV IO --> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-csv</artifactId> + <version>1.8</version> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>core.api</artifactId> + </dependency> +<!-- + <dependency> + <groupId>org.mariuszgromada.math</groupId> + <artifactId>MathParser.org-mXparser</artifactId> + <version>4.4.2</version> + </dependency> +--> + <!-- dependencies to language models --> + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.ddl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.dl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.eal</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.el</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.instance</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.mll</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <!-- dependencies to DSLs --> + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.ddl.dsl</artifactId> + <version>${evoal.languages.version}</version> + <scope>runtime</scope> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.dl.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.eal.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.el.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.instance.dsl</artifactId> + <version>${evoal.languages.version}</version> + <scope>runtime</scope> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.mll.dsl</artifactId> + <version>${evoal.languages.version}</version> + <scope>runtime</scope> + </dependency> + + <!-- Xtext --> + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.ecore</artifactId> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.common</artifactId> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>org.eclipse.xtext</groupId> + <artifactId>org.eclipse.xtext</artifactId> + <scope>compile</scope> + </dependency> + + <!-- Printing tables to the console --> + <!-- + <dependency> + <groupId>de.vandermeer</groupId> + <artifactId>asciitable</artifactId> + <version>0.3.2</version> + </dependency> + --> + + <dependency> + <groupId>io.jenetics</groupId> + <artifactId>jenetics.ext</artifactId> + <version>${jenetics.version}</version> + </dependency> + + <dependency> + <groupId>org.decimal4j</groupId> + <artifactId>decimal4j</artifactId> + <version>1.0.3</version> + <scope>compile</scope> + </dependency> + + + <!-- XML IO --> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>generator.main</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>copy-dependencies</goal> + </goals> + <configuration> + <outputDirectory>${project.build.directory}/modules</outputDirectory> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifest> + <mainClass>de.evoal.core.main.Evoal</mainClass> + <addClasspath>true</addClasspath> + <classpathPrefix>modules/</classpathPrefix> + </manifest> + </archive> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <configuration> + <compilerArgs> + <arg>--add-exports</arg> + <arg>io.jenetics.base/io.jenetics.internal.math=de.evoal.core.main</arg> + </compilerArgs> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/Evoal.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/Evoal.java new file mode 100644 index 0000000000000000000000000000000000000000..36da25acf27caadb7298e88598495d96c9b488a4 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/Evoal.java @@ -0,0 +1,65 @@ +package de.evoal.core.main; + +import de.evoal.core.api.board.Blackboard; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.MainClass; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.spi.Bean; + +import lombok.extern.slf4j.Slf4j; +import org.apache.deltaspike.cdise.api.CdiContainer; +import org.apache.deltaspike.cdise.api.CdiContainerLoader; +import org.apache.deltaspike.core.api.provider.BeanProvider; + +import java.util.Set; + +/** + * Main class for EvoAl. The concrete runtime behaviour depends on the used blackboard + * configuration. + */ +@Slf4j +public final class Evoal { + public static void main(final String ... args) { + log.info("Starting up EvoAl"); + + log.info("Booting CDI container"); + final CdiContainer cdiContainer = CdiContainerLoader.getCdiContainer(); + cdiContainer.boot(); + cdiContainer.getContextControl().startContext(ApplicationScoped.class); + + log.info("Setting up black board"); + final Blackboard board = BeanProvider.getContextualReference(Blackboard.class); + board.readArguments(args); + + log.info("Fetching main class and handing over control"); + try { + final String mainName = board.get(BlackboardEntry.MAIN); + MainClass main = null; + + try { + main = BeanProvider.getContextualReference(mainName, false, MainClass.class); + } catch(final Throwable e) { + logMainError(); + System.exit(1); + } + + main.run(); + } catch (final Throwable e) { + log.error("Main class threw an exception.", e); + } + + log.info("Shutting down CDI container"); + cdiContainer.shutdown(); + } + + private static void logMainError() { + log.error("Name of main class was not set. Please specify the main class via command line (-BMAIN=<name>)."); + Set<Bean<MainClass>> beans = BeanProvider.getBeanDefinitions(MainClass.class, true, true); + log.error(" possible names are:"); + + for(final Bean<MainClass> bean : beans) { + log.error(" {}", bean.getName()); + } + + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/cdi/producer/BlackboardValueProducer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/cdi/producer/BlackboardValueProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..7b2bda9d70b0974eca43722b3f4de8b8c074ad26 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/cdi/producer/BlackboardValueProducer.java @@ -0,0 +1,75 @@ +package de.evoal.core.main.cdi.producer; + +import de.evoal.core.api.cdi.BlackboardValue; +import de.evoal.core.api.board.Blackboard; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; + +import java.io.File; +import java.util.Map; + +@ApplicationScoped +public class BlackboardValueProducer { + @Produces + @BlackboardValue(BlackboardEntry.EA_CONFIGURATION) + public Integer injectIntegerValue(final InjectionPoint ip, final Blackboard board) { + final BlackboardValue value = ip.getAnnotated().getAnnotation(BlackboardValue.class); + final Object result = board.get(value.value()); + + if(result instanceof Number) { + return ((Number)result).intValue(); + } else if(result instanceof String) { + return Integer.parseInt((String)result); + } + + throw new IllegalArgumentException("Unable to handle type " + result.getClass()); + } + + @Produces + @BlackboardValue(BlackboardEntry.EA_CONFIGURATION) + public String injectStringValue(final InjectionPoint ip, final Blackboard board) { + final BlackboardValue value = ip.getAnnotated().getAnnotation(BlackboardValue.class); + + return castValue(board.get(value.value())); + } + + @Produces + @BlackboardValue(BlackboardEntry.EA_CONFIGURATION) + public File injectFileValue(final InjectionPoint ip, final Blackboard board) { + final BlackboardValue value = ip.getAnnotated().getAnnotation(BlackboardValue.class); + + return castValue(board.get(value.value())); + } + + @Produces + @BlackboardValue(BlackboardEntry.EA_CONFIGURATION) + public Map<String, Object> injectMapValue(final InjectionPoint ip, final Blackboard board) { + final BlackboardValue value = ip.getAnnotated().getAnnotation(BlackboardValue.class); + + return castValue(board.get(value.value())); + } + + @Produces + @BlackboardValue(BlackboardEntry.EA_CONFIGURATION) + public PropertiesSpecification injectPropertiesSpecification(final InjectionPoint ip, final Blackboard board) { + final BlackboardValue value = ip.getAnnotated().getAnnotation(BlackboardValue.class); + + return castValue(board.get(value.value())); + } + + @Produces + @BlackboardValue(BlackboardEntry.EA_CONFIGURATION) + public Properties injectProperties(final InjectionPoint ip, final Blackboard board) { + final BlackboardValue value = ip.getAnnotated().getAnnotation(BlackboardValue.class); + + return castValue(board.get(value.value())); + } + + private <T> T castValue(final Object object) { + return (T) object; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/cdi/producer/DataConstraintProducer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/cdi/producer/DataConstraintProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..b05e66bdaedc353cbf172b36aad3788c1934c098 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/cdi/producer/DataConstraintProducer.java @@ -0,0 +1,58 @@ +package de.evoal.core.main.cdi.producer; + +import de.evoal.languages.model.ddl.DataDescriptionModel; +import de.evoal.languages.model.eal.DataReference; +import de.evoal.languages.model.eal.EAModel; +import de.evoal.languages.model.el.Expression; +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EObject; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; +import javax.inject.Named; +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +@ApplicationScoped +public class DataConstraintProducer { + @Produces @Dependent @Named("data-constraints") + public Collection<Expression> produceDataInformation(final EAModel model) { + final TreeIterator<EObject> iterator = model.eAllContents(); + Iterable<EObject> iterable = () -> iterator; + + // collect all expressions associated to the data definitions directly + Set<Expression> result = StreamSupport.stream(iterable.spliterator(), false) + .filter(DataReference.class::isInstance) + .map(DataReference.class::cast) + .map(DataReference::getDefinition) + .flatMap(d -> d.getConstraints().stream()) + .collect(Collectors.toSet()); + + // collect all expressions associated to the files + final TreeIterator<EObject> iterator2 = model.eAllContents(); + iterable = () -> iterator2; + StreamSupport.stream(iterable.spliterator(), false) + .filter(DataReference.class::isInstance) + .map(DataConstraintProducer::findModel) + .filter(Objects::nonNull) + .distinct() + .flatMap(m -> m.getConstraints().stream()) + .forEach(result::add); + + return result; + } + + private static DataDescriptionModel findModel(final EObject reference) { + EObject current = reference; + + while(current != null && !(current instanceof DataDescriptionModel)) { + current = current.eContainer(); + } + + return (DataDescriptionModel)current; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/cdi/producer/EvolutionaryAlgorithmModelLoader.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/cdi/producer/EvolutionaryAlgorithmModelLoader.java new file mode 100644 index 0000000000000000000000000000000000000000..31c00400c3e0ad4968e8a322859181f6b3d81bfe --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/cdi/producer/EvolutionaryAlgorithmModelLoader.java @@ -0,0 +1,109 @@ +package de.evoal.core.main.cdi.producer; + +import com.google.inject.Injector; +import de.evoal.core.api.board.Blackboard; +import de.evoal.core.api.board.BlackboardEntry; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Produces; + +import de.evoal.languages.model.dl.dsl.DefinitionLanguageStandaloneSetup; +import de.evoal.languages.model.eal.EAModel; +import de.evoal.languages.model.eal.dsl.EvolutionaryAlgorithmLanguageStandaloneSetup; +import de.evoal.languages.model.dl.impl.DlPackageImpl; +import de.evoal.languages.model.eal.impl.EALPackageImpl; +import de.evoal.languages.model.el.dsl.ExpressionLanguageStandaloneSetup; +import de.evoal.languages.model.el.impl.ELPackageImpl; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.resource.XtextResourceSet; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.File; + +@ApplicationScoped +@Slf4j +public class EvolutionaryAlgorithmModelLoader { + @Inject + private Blackboard board; + private EAModel model; + + public void load(final @Observes BlackboardEntry entry) { + if(!BlackboardEntry.EA_CONFIGURATION_FILE.equals(entry)) { + return; + } + + final String configurationFileName = board.get(BlackboardEntry.EA_CONFIGURATION_FILE); + log.info("Loading evolutionary algorithm configuration from {}.", configurationFileName); + + final File configurationFile = new File(configurationFileName); + if(!configurationFile.exists() || ! configurationFile.canRead()) { + log.error("Unable to read evolutionary algorithm configuration file '{}'", configurationFileName); + throw new IllegalArgumentException("Unable to read evolutionary algorithm configuration file: " + configurationFileName); + } + + + initializeEMF(); + + final Injector ealInjector = new EvolutionaryAlgorithmLanguageStandaloneSetup().createInjectorAndDoEMFRegistration(); + // do not remove the following line even if the injector is not used. Otherwise, parsing eal files breaks. + final Injector idlInjector = new DefinitionLanguageStandaloneSetup().createInjectorAndDoEMFRegistration(); + + final XtextResourceSet resourceSet = ealInjector.getInstance(XtextResourceSet.class); + resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE); + resourceSet.addLoadOption(XtextResource.OPTION_ENCODING, "UTF-8"); + + try { + log.info("Continue by loading the EAL file."); + final URI modelURI = URI.createFileURI(configurationFile.getAbsolutePath()); + + log.info("Loading ea model from URI {}.", modelURI); + + final Resource resource = resourceSet.getResource(modelURI, true); + resource.load(resourceSet.getLoadOptions()); + if(!resource.getErrors().isEmpty()) { + for(Resource.Diagnostic diagnostic : resource.getErrors()) { + log.error("Error while processing rule '{}': {}", configurationFile, diagnostic); + } + } + if(!resource.getWarnings().isEmpty()) { + for(Resource.Diagnostic diagnostic : resource.getWarnings()) { + log.error("Warning while processing rule '{}': {}", configurationFile, diagnostic); + } + } + + model = (EAModel) resource.getContents().get(0); + board.bind(BlackboardEntry.EA_CONFIGURATION, model); + } catch (final Exception e) { + log.error("Unable to evolutionary algorithm configuration file '{}'.", configurationFile, e); + } + } + + /** + * Initialize the model packages and perform the parser setup. + */ + private void initializeEMF() { + EALPackageImpl.init(); + DlPackageImpl.init(); + EALPackageImpl.init(); + + /* + if (!EPackage.Registry.INSTANCE.containsKey("https://www.evoal.de/languages/idl/1.0.0")) { + EPackage.Registry.INSTANCE.put("https://www.evoal.de/languages/idl/1.0.0", DlPackage.eINSTANCE); + } +*/ + ExpressionLanguageStandaloneSetup.doSetup(); + DefinitionLanguageStandaloneSetup.doSetup(); + EvolutionaryAlgorithmLanguageStandaloneSetup.doSetup(); + } + + @Produces + @Dependent + public EAModel getConfiguration() { + return model; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/ConstraintProducer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/ConstraintProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..85a97988ccf7de33ca93962f96a33f2a4d86ddd0 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/ConstraintProducer.java @@ -0,0 +1,69 @@ +package de.evoal.core.main.ddl.constraint; + +import de.evoal.core.api.ea.constraints.model.Constraint; +import de.evoal.core.api.ea.constraints.model.Constraints; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.properties.PropertiesSpecification; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import de.evoal.core.main.ddl.constraint.ast.ConditionConverter; +import de.evoal.core.main.ddl.el.ElHelper; +import de.evoal.languages.model.ddl.FunctionName; +import de.evoal.languages.model.el.Call; +import de.evoal.languages.model.el.Expression; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Named; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; + +@ApplicationScoped +@Slf4j +public class ConstraintProducer { + private PropertiesSpecification specification; + + @Produces + @ApplicationScoped + public Constraints create(final @Named("data-constraints") Collection<Expression> constraints, + final CustomCodec codec) { + this.specification = specification; + + final Constraints result = new Constraints(); + + constraints.stream() + .map(ElHelper::findCall) + .filter(Objects::nonNull) + .map(Call.class::cast) + .filter(c -> "constraint".equals(((FunctionName)c.getFunction()).getDefinition().getName())) + .map(this::convert) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(result.getConstraints()::add); + + log.info("Loaded {} constraints.", result.getConstraints().size()); + + return result; + } + + private Optional<Constraint> convert(final Call constraint) { + if(constraint.getParameters().size() != 2) { + log.error("Constraint has more than two parameters. Skipping."); + return Optional.empty(); + } + + final Constraint result = new Constraint(); + result.setGroup(ElHelper.findString(constraint.getParameters().get(1))); + + final ConditionConverter converter = new ConditionConverter(specification); + converter.doSwitch(constraint.getParameters().get(0)); + + result.setFunction(converter.getFunction()); + result.setUsedProperties(converter.getUsedProperties()); + result.setConstraintType(converter.getType()); + + return Optional.of(result); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/ast/ConditionConverter.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/ast/ConditionConverter.java new file mode 100644 index 0000000000000000000000000000000000000000..8c7ae6fcd883baf02f287c70f2b3f8184bb6d445 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/ast/ConditionConverter.java @@ -0,0 +1,253 @@ +package de.evoal.core.main.ddl.constraint.ast; + +import de.evoal.core.api.utils.Requirements; +import de.evoal.core.api.ea.constraints.model.ConstraintType; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.languages.model.eal.DataReference; +import de.evoal.languages.model.el.*; +import de.evoal.languages.model.el.util.ELSwitch; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; + +public class ConditionConverter extends ELSwitch<Object> { + private Function<Properties, Double> function; + private final List<PropertySpecification> usedProperties = new ArrayList<>(); + private final PropertiesSpecification specification; + private ConstraintType type; + + public ConditionConverter(final PropertiesSpecification specification) { + this.specification = specification; + } + + @Override + public Object caseOrExpression(OrExpression object) { + Requirements.requireSize(object.getSubExpressions(), 1); + + doSwitch(object.getSubExpressions().get(0)); + + return null; + } + + @Override + public Object caseXorExpression(XorExpression object) { + Requirements.requireSize(object.getSubExpressions(), 1); + + doSwitch(object.getSubExpressions().get(0)); + + return null; + } + + @Override + public Object caseAndExpression(AndExpression object) { + Requirements.requireSize(object.getSubExpressions(), 1); + + doSwitch(object.getSubExpressions().get(0)); + + return null; + } + + @Override + public Object caseNotExpression(NotExpression object) { + Requirements.requireFalse(object.isNegated()); + + doSwitch(object.getOperand()); + + return null; + } + + @Override + public Object caseComparisonExpression(ComparisonExpression object) { + Requirements.requireSize(object.getComparison(), 1); + + final Function<Properties, Double> leftValue = (Function<Properties, Double>) doSwitch(object.getLeftOperand()); + final Function<Properties, Double> rightValue = (Function<Properties, Double>) doSwitch(object.getComparison().get(0).getSubExpression()); + + switch (object.getComparison().get(0).getOperator()) { + case EQUAL: + case GREATER_EQUAL: + case GREATER_THAN: { + function = properties -> leftValue.apply(properties) - rightValue.apply(properties); + break; + } + case LESS_EQUAL: + case LESS_THAN: + { + function = properties -> rightValue.apply(properties) - leftValue.apply(properties); + break; + } + case UNEQUAL: { + throw new IllegalArgumentException("Unequal is not allowed"); + } + } + + switch (object.getComparison().get(0).getOperator()) { + case EQUAL: + type = ConstraintType.Equality; + break; + + case GREATER_EQUAL: + case GREATER_THAN: + case LESS_EQUAL: + case LESS_THAN: + type = ConstraintType.Inequality; + break; + + case UNEQUAL: { + throw new IllegalArgumentException("Unequal is not allowed"); + } + } + + return null; + } + + @Override + public Object caseAddOrSubtractExpression(final AddOrSubtractExpression object) { + Function<Properties, Double> value = (Function<Properties, Double>) doSwitch(object.getLeftOperand()); + + for(int i = 0; i < object.getOperators().size(); ++i) { + final AddOrSubtractOperator operator = object.getOperators().get(i); + final Function<Properties, Double> rOp = (Function<Properties, Double>) doSwitch(object.getOperands().get(i)); + + switch (operator) { + case ADD: { + final Function<Properties, Double> lOp = value; + value = properties -> lOp.apply(properties) + rOp.apply(properties); + break; + } + + case SUBTRACT: { + final Function<Properties, Double> lOp = value; + value = properties -> lOp.apply(properties) - rOp.apply(properties); + break; + } + } + } + return value; + } + + @Override + public Object caseMultiplyDivideModuloExpression(MultiplyDivideModuloExpression object) { + Function<Properties, Double> value = (Function<Properties, Double>) doSwitch(object.getLeftOperand()); + + for(int i = 0; i < object.getOperators().size(); ++i) { + final MultiplyDivideModuloOperator operator = object.getOperators().get(i); + final Function<Properties, Double> rOp = (Function<Properties, Double>) doSwitch(object.getOperands().get(i)); + + switch (operator) { + case DIVIDE: { + final Function<Properties, Double> lOp = value; + value = properties -> lOp.apply(properties) / rOp.apply(properties); + break; + } + + case MODULO: { + final Function<Properties, Double> lOp = value; + value = properties -> lOp.apply(properties) % rOp.apply(properties); + break; + } + + case MULTIPLY: { + final Function<Properties, Double> lOp = value; + value = properties -> lOp.apply(properties) * rOp.apply(properties); + break; + } + } + } + return value; + } + + @Override + public Object casePowerOfExpression(PowerOfExpression object) { + Function<Properties, Double> value = (Function<Properties, Double>) doSwitch(object.getLeftOperand()); + + if(object.getRightOperand() != null) { + final Function<Properties, Double> lOp = value; + final Function<Properties, Double> rOp = (Function<Properties, Double>) doSwitch(object.getRightOperand()); + + value = properties -> Math.pow(lOp.apply(properties), rOp.apply(properties)); + } + return value; + } + + @Override + public Object caseUnaryAddOrSubtractExpression(UnaryAddOrSubtractExpression object) { + Function<Properties, Double> value = (Function<Properties, Double>) doSwitch(object.getSubExpression()); + + for(final AddOrSubtractOperator operator : object.getOperators()) { + switch (operator) { + case ADD: { + value = value; + break; + } + + case SUBTRACT: { + final Function<Properties, Double> lOp = value; + value = properties -> -lOp.apply(properties); + break; + } + } + } + return value; } + + @Override + public Object caseIntegerLiteral(final IntegerLiteral object) { + return (Function<Properties, Double>) properties -> (double)object.getValue(); + } + + @Override + public Object caseDoubleLiteral(final DoubleLiteral object) { + return (Function<Properties, Double>) properties -> object.getValue(); + } + + @Override + public Object caseStringLiteral(final StringLiteral object) { + return (Function<Properties, Double>) properties -> Double.parseDouble(object.getValue()); + } + + @Override + public Object caseValueReference(final ValueReference object) { + if(!(object instanceof DataReference)) { + throw new IllegalStateException("Value reference is not a data reference."); + } + + final DataReference reference = (DataReference)object; + final String propertyName = reference.getDefinition().getName(); + final int propertyIndex = specification.indexOf(new PropertySpecification(propertyName)); + + usedProperties.add(specification.getProperties().get(propertyIndex)); + + return (Function<Properties, Double>) properties -> properties.get(propertyIndex); + } + + @Override + public Object caseBooleanLiteral(final BooleanLiteral object) { + throw new IllegalArgumentException("Boolean values are not supported"); + } + + @Override + public Object caseCall(Call object) { + throw new IllegalArgumentException("Calls are not supported"); + } + + @Override + public Object caseParantheses(final Parantheses object) { + return doSwitch(object.getSubExpression()); + } + + public Function<Properties, Double> getFunction() { + return function; + } + + public List<PropertySpecification> getUsedProperties() { + return Collections.unmodifiableList(usedProperties); + } + + public ConstraintType getType() { + return type; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/CalculationFactory.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/CalculationFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..2956d3cc2f28dbd9509d5d9941d971018e1d03fe --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/CalculationFactory.java @@ -0,0 +1,34 @@ +package de.evoal.core.main.ddl.constraint.strategies; + +import de.evoal.core.api.ea.constraints.model.Constraint; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.ConfigurationValue; +import javax.enterprise.context.ApplicationScoped; + +import de.evoal.core.api.utils.LanguageHelper; +import de.evoal.languages.model.instance.Instance; +import org.apache.deltaspike.core.api.provider.BeanProvider; + +import javax.inject.Inject; + +@ApplicationScoped +public class CalculationFactory { + private final Instance handlerConfigurations; + + @Inject + public CalculationFactory(final @ConfigurationValue(entry = BlackboardEntry.EA_CONFIGURATION, access = "algorithm.constraint_handling") Instance handlerConfiguration) { + handlerConfigurations = handlerConfiguration; + } + + public CalculationStrategy create(final Constraint constraint) { + final Instance handlerConfig = LanguageHelper.lookup(handlerConfigurations, constraint.getGroup()); + final Instance calculationConfig = LanguageHelper.lookup(handlerConfigurations, "calculation"); + + final String calculationName = LanguageHelper.lookup(calculationConfig, "name"); + + final CalculationStrategy strategy = BeanProvider.getContextualReference(calculationName, false, CalculationStrategy.class); + strategy.init(constraint, calculationConfig); + + return strategy; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/CalculationStrategy.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/CalculationStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..6b68a19ff92e0312efb249695825cb73e87268b0 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/CalculationStrategy.java @@ -0,0 +1,29 @@ +package de.evoal.core.main.ddl.constraint.strategies; + +import de.evoal.core.api.ea.constraints.strategies.CalculationResult; +import de.evoal.core.api.ea.constraints.model.Constraint; +import de.evoal.core.api.properties.Properties; +import de.evoal.languages.model.instance.Instance; +import lombok.NonNull; + +/** + * A calculation strategy calculates the final difference of a {@link Constraint}. + * The actual implementations can modify the result of the constraint evaluation it its own will. + */ +public interface CalculationStrategy { + /** + * Calculates the constraint handling result for the given {@code properties}. + * + * @param properties Properties of an individual to check. + * @return A non-null non-empty list of constraint handling results. + */ + public @NonNull CalculationResult calculate(final Properties properties); + + /** + * Initialises the strategy with the constraint and the calculation configuration. + * + * @param constraint The corresponding constraint. + * @param configuration The handler configuration. + */ + void init(final Constraint constraint, final Instance configuration); +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/calculations/NormalCalculation.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/calculations/NormalCalculation.java new file mode 100644 index 0000000000000000000000000000000000000000..e9092ae4299b06c1d419a877ccbc5ce7dd62b24e --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/calculations/NormalCalculation.java @@ -0,0 +1,30 @@ +package de.evoal.core.main.ddl.constraint.strategies.calculations; + +import de.evoal.core.api.ea.constraints.model.Constraint; +import de.evoal.core.api.ea.constraints.strategies.CalculationResult; +import de.evoal.core.main.ddl.constraint.strategies.CalculationStrategy; +import de.evoal.core.api.properties.Properties; +import javax.enterprise.context.Dependent; + +import de.evoal.languages.model.instance.Instance; +import lombok.NonNull; + +import javax.inject.Named; + +@Dependent +@Named("normal") +public class NormalCalculation implements CalculationStrategy { + private Instance configuration; + private Constraint constraint; + + @Override + public @NonNull CalculationResult calculate(final Properties properties) { + return new CalculationResult(constraint.apply(properties)); + } + + @Override + public void init(final Constraint constraint, final Instance configuration) { + this.configuration = configuration; + this.constraint = constraint; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/calculations/StandardDeviationCalculation.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/calculations/StandardDeviationCalculation.java new file mode 100644 index 0000000000000000000000000000000000000000..694f792d4e6bfa853361bd3b195a6ec66d169324 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/calculations/StandardDeviationCalculation.java @@ -0,0 +1,106 @@ +package de.evoal.core.main.ddl.constraint.strategies.calculations; + +import de.evoal.core.api.ea.constraints.model.Constraint; +import de.evoal.core.api.ea.constraints.model.ConstraintResult; +import de.evoal.core.api.ea.constraints.strategies.CalculationResult; +import de.evoal.core.api.utils.LanguageHelper; +import de.evoal.core.main.ddl.constraint.strategies.CalculationStrategy; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; +import javax.enterprise.context.Dependent; + +import de.evoal.core.main.ddl.deviation.model.Deviations; +import de.evoal.languages.model.instance.Instance; +import lombok.NonNull; +import org.apache.commons.math3.util.Pair; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.*; +import java.util.stream.Collectors; + +@Dependent +@Named("standard-deviation") +public class StandardDeviationCalculation implements CalculationStrategy { + @Inject + private Deviations deviations; + + @Inject @Named("source-properties-specification") + private PropertiesSpecification source; + + private Instance configuration; + private Constraint constraint; + private double factor; + private List<Pair<Integer, Double>> allowedDeviations; + + @Override + public @NonNull CalculationResult calculate(final Properties properties) { + return new CalculationResult(calculateMinimalDifference(0, new HashMap<>(), properties)); + } + + private ConstraintResult calculateMinimalDifference(final int index, final Map<Integer, Double> differences, final Properties properties) { + if(index == allowedDeviations.size()) { + final Properties adaptedProperties = new Properties(properties); + + for(final Map.Entry<Integer, Double> entry : differences.entrySet()) { + final Integer propIndex = entry.getKey(); + final double value = adaptedProperties.get(propIndex) + entry.getValue(); + adaptedProperties.put(propIndex, value); + } + + return constraint.apply(adaptedProperties); + } + + final Pair<Integer, Double> deviation = allowedDeviations.get(index); + + // no deviation + differences.put(deviation.getFirst(), 0.0); + ConstraintResult minimalResult = calculateMinimalDifference(index + 1, differences, properties); + + if(CalculationResult.isSuccessful(minimalResult) || deviation.getSecond() == 0.0) { + return minimalResult; + } + + // - deviation + { + differences.put(deviation.getFirst(), -deviation.getSecond() * factor); + ConstraintResult result = calculateMinimalDifference(index + 1, differences, properties); + + if(CalculationResult.isSuccessful(result)) { + return result; + } else if(Math.abs(result.getComparisonDifference()) < Math.abs(minimalResult.getComparisonDifference())) { + minimalResult = result; + } + } + + // + deviation + { + differences.put(deviation.getFirst(), deviation.getSecond() * factor); + ConstraintResult result = calculateMinimalDifference(index + 1, differences, properties); + + if(CalculationResult.isSuccessful(result)) { + return result; + } else if(Math.abs(result.getComparisonDifference()) < Math.abs(minimalResult.getComparisonDifference())) { + minimalResult = result; + } + + return minimalResult; + } + } + + @Override + public void init(final Constraint constraint, final Instance configuration) { + this.configuration = configuration; + this.constraint = constraint; + this.factor = LanguageHelper.lookup(configuration, "factor"); + + final List<PropertySpecification> sourceProperties = constraint.getUsedProperties(); + + allowedDeviations = sourceProperties + .stream() + .map(ps -> new Pair<PropertySpecification, Optional<Double>>(ps, deviations.find(ps))) + .map(p -> new Pair<Integer, Double>(source.indexOf(p.getFirst()), p.getSecond().orElse(0.0))) + .collect(Collectors.toList()); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/JeneticsConstraintProducer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/JeneticsConstraintProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..a150880392266591fe3c8b0bc7b0ec66e858cc0e --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/JeneticsConstraintProducer.java @@ -0,0 +1,42 @@ +package de.evoal.core.main.ddl.constraint.strategies.constraint; + +import de.evoal.core.api.ea.constraints.model.Constraint; +import de.evoal.core.api.ea.constraints.model.Constraints; +import de.evoal.core.main.ddl.constraint.strategies.CalculationFactory; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.ConfigurationValue; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.main.ddl.constraint.utils.ConfigurationUtils; +import de.evoal.languages.model.instance.Attribute; +import de.evoal.languages.model.instance.Instance; +import de.evoal.languages.model.instance.Misc; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import java.util.*; +import java.util.stream.Collectors; + +@ApplicationScoped +public class JeneticsConstraintProducer { + @Produces + public List<io.jenetics.engine.Constraint> create( + final @ConfigurationValue(entry = BlackboardEntry.EA_CONFIGURATION, access = "algorithm.constraint_handling") Instance handlerConfiguration, + final Constraints constraints, + final CustomCodec codec, + final CalculationFactory factory) { + // collect group information to handle + final List<Attribute> groups = ConfigurationUtils.findByHandlerName(handlerConfiguration, "killAtBirth"); + final Set<String> allGroups = groups.stream().map(e -> ((Misc)e.getName()).getName()).collect(Collectors.toSet()); + final List<Constraint> listOfConstraints = constraints.getConstraints(); + + final List<io.jenetics.engine.Constraint> result = listOfConstraints + .stream() + .filter(c -> allGroups.contains(c.getGroup())) + .map(factory::create) + .map(s -> new JeneticsConstraintStrategy(s, codec, new RandomGenotypeStrategy())) + .collect(Collectors.toList()); + + return result; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/JeneticsConstraintStrategy.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/JeneticsConstraintStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..795b7ba177d75d58317665644cf7b49146cdb858 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/JeneticsConstraintStrategy.java @@ -0,0 +1,36 @@ +package de.evoal.core.main.ddl.constraint.strategies.constraint; + +import de.evoal.core.main.ddl.constraint.strategies.CalculationStrategy; +import de.evoal.core.api.ea.constraints.strategies.HandlingStrategy; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.properties.Properties; +import io.jenetics.Gene; +import io.jenetics.Phenotype; + +public class JeneticsConstraintStrategy< + G extends Gene<?, G>, + C extends Comparable<? super C> + > implements io.jenetics.engine.Constraint<G, C>, HandlingStrategy { + + private final CalculationStrategy calculation; + private final CustomCodec<G> codec; + private final RepairStrategy repair; + + public JeneticsConstraintStrategy(final CalculationStrategy calculation, final CustomCodec<G> codec, final RepairStrategy repair) { + this.calculation = calculation; + this.codec = codec; + this.repair = repair; + } + + @Override + public boolean test(final Phenotype<G, C> individual) { + final Properties properties = codec.decode(individual.genotype()); + + return calculation.calculate(properties).isSuccessful(); + } + + @Override + public Phenotype<G, C> repair(final Phenotype<G, C> individual, final long generation) { + return repair.apply(individual, generation); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/RandomGenotypeStrategy.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/RandomGenotypeStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..e0659a82dbf31b1c5008c81a140282b0f9f78f70 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/RandomGenotypeStrategy.java @@ -0,0 +1,14 @@ +package de.evoal.core.main.ddl.constraint.strategies.constraint; + +import io.jenetics.Gene; +import io.jenetics.Genotype; +import io.jenetics.Phenotype; + +public class RandomGenotypeStrategy<G extends Gene<?, G>, C extends Comparable<? super C>> implements RepairStrategy<G, C> { + @Override + public Phenotype apply(final Phenotype<G, C> individual, long generation) { + final Genotype<G> newInstance = individual.genotype().newInstance(); + + return Phenotype.of(newInstance, generation); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/RepairStrategy.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/RepairStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..2fe93245476d30087c88958d203bc092d64ab3dd --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/constraint/RepairStrategy.java @@ -0,0 +1,8 @@ +package de.evoal.core.main.ddl.constraint.strategies.constraint; + +import io.jenetics.Gene; +import io.jenetics.Phenotype; + +public interface RepairStrategy<G extends Gene<?, G>, C extends Comparable<? super C>> { + public Phenotype<G,C> apply(Phenotype<G,C> individual, long generation); +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/fitness/MalusFunctionProducer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/fitness/MalusFunctionProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..e2ca15700c632bab98fe46bba5a9ed44bbdcd930 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/fitness/MalusFunctionProducer.java @@ -0,0 +1,90 @@ +package de.evoal.core.main.ddl.constraint.strategies.fitness; + +import de.evoal.core.api.ea.constraints.model.Constraint; +import de.evoal.core.api.ea.constraints.model.Constraints; +import de.evoal.core.api.ea.constraints.strategies.fitness.MalusForFitnessStrategy; +import de.evoal.core.api.ea.constraints.strategies.fitness.MalusFunction; +import de.evoal.core.main.ddl.constraint.strategies.fitness.internal.MalusForFitnessFunction; +import de.evoal.core.api.utils.LanguageHelper; +import de.evoal.core.main.ddl.constraint.strategies.CalculationFactory; +import de.evoal.core.main.ddl.constraint.strategies.CalculationStrategy; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.ConfigurationValue; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import de.evoal.core.main.ddl.constraint.utils.ConfigurationUtils; +import de.evoal.languages.model.instance.Attribute; +import de.evoal.languages.model.instance.Instance; +import de.evoal.languages.model.instance.Misc; +import org.apache.commons.math3.util.Pair; + +import javax.inject.Named; +import java.util.*; + +@ApplicationScoped +public class MalusFunctionProducer { + @ApplicationScoped @Produces + public MalusForFitnessStrategy create( + final @ConfigurationValue(entry = BlackboardEntry.EA_CONFIGURATION, access = "algorithm.constraint_handling") Instance handlerConfiguration, + final Constraints constraints, + final @Named("source-properties-specification") PropertiesSpecification source, + final @Named("output-dependencies") PropertiesDependencies dependencies, + final @Named("target-properties-specification") PropertiesSpecification target, + final CalculationFactory factory) { + final MalusForFitnessStrategy resultingFunction = new MalusForFitnessStrategy(target.size()); + + // collect group information to handle + final List<Attribute> groups = ConfigurationUtils.findByHandlerName(handlerConfiguration, "malusForFitness"); + + // collect all constraints for each index (of the appropriate groups) + final List<List<Pair<Constraint, Attribute>>> constraintsForIndex = new ArrayList<>(source.size()); + for(int index = 0; index < source.size(); ++index) { + constraintsForIndex.add(new ArrayList<>()); + } + + for(final Constraint constraint : constraints.getConstraints()) { + final String group = constraint.getGroup(); + + for(final Attribute info : groups) { + if(((Misc)info.getName()).getName().equals(group)) { + for(final PropertySpecification ps : constraint.getUsedProperties()) { + final int index = source.indexOf(ps); + constraintsForIndex.get(index).add(new Pair<>(constraint, info)); + } + } + } + } + + // build MalusFunctionParts + for(int index = 0; index < target.size(); ++index) { + final Set<Constraint> applied = new HashSet<>(); + + for(final PropertySpecification ips : dependencies.get(target.getProperties().get(index))) { + final int ipsIndex = source.indexOf(ips); + + for(final Pair<Constraint, Attribute> pair : constraintsForIndex.get(ipsIndex)) { + final Constraint constraint = pair.getFirst(); + final Attribute configuration = pair.getSecond(); + + // prevent constraints from being applied multiple times per fitness value + if(applied.contains(constraint)) { + continue; + } + applied.add(constraint); + + final CalculationStrategy calculation = factory.create(constraint); + + final String handlerName = LanguageHelper.lookup((Instance) configuration.getValue(), "name"); + + final MalusFunction strategy = new MalusForFitnessFunction(constraint, LanguageHelper.lookup((Instance)configuration.getValue(), "handling"), index) ; + resultingFunction.add(index, strategy); + } + } + } + + return resultingFunction; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/fitness/PropertiesDependencies.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/fitness/PropertiesDependencies.java new file mode 100644 index 0000000000000000000000000000000000000000..15802d29829c39ffca662555defbca5b03133378 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/fitness/PropertiesDependencies.java @@ -0,0 +1,27 @@ +package de.evoal.core.main.ddl.constraint.strategies.fitness; + +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; + +import java.util.*; + +/** + * Small utility class for calculating the dependencies between input and output dependencies. + */ +public class PropertiesDependencies { + private final Map<PropertySpecification, Set<PropertySpecification>> dependencies = new HashMap<>(); + + public PropertiesDependencies(final PropertiesSpecification specification) { + for(final PropertySpecification ps : specification.getProperties()) { + dependencies.put(ps, new HashSet<>()); + } + } + + public void add(final PropertySpecification target, final PropertiesSpecification deps) { + dependencies.get(target).addAll(deps.getProperties()); + } + + public Set<PropertySpecification> get(final PropertySpecification specification) { + return dependencies.get(specification); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/fitness/internal/MalusForFitnessFunction.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/fitness/internal/MalusForFitnessFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..f49486f735a90345e28bd21a4092330653bf096d --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/strategies/fitness/internal/MalusForFitnessFunction.java @@ -0,0 +1,25 @@ +package de.evoal.core.main.ddl.constraint.strategies.fitness.internal; + +import de.evoal.core.api.ea.constraints.model.Constraint; +import de.evoal.core.api.ea.constraints.model.ConstraintResult; +import de.evoal.core.api.ea.constraints.strategies.fitness.MalusFunction; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.utils.LanguageHelper; +import de.evoal.languages.model.instance.Instance; + +public class MalusForFitnessFunction implements MalusFunction { + private final Constraint constraint; + private final double smoothing; + + public MalusForFitnessFunction(final Constraint constraint, final Instance configuration, final int index) { + this.constraint = constraint; + smoothing = LanguageHelper.lookup(configuration, "smoothing"); + } + + @Override + public double apply(final Properties properties, double fitnessValue) { + final ConstraintResult result = constraint.apply(properties); + + return fitnessValue - smoothing * Math.abs(result.getComparisonDifference()); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/utils/ConfigurationUtils.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/utils/ConfigurationUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..acc5648eece616021fa5f8e86e756d0f23cf870c --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/constraint/utils/ConfigurationUtils.java @@ -0,0 +1,28 @@ +package de.evoal.core.main.ddl.constraint.utils; + +import de.evoal.core.api.utils.LanguageHelper; +import de.evoal.languages.model.instance.Attribute; +import de.evoal.languages.model.instance.Instance; + +import java.util.ArrayList; +import java.util.List; + +public final class ConfigurationUtils { + private ConfigurationUtils() { + } + + public static List<Attribute> findByHandlerName(Instance configuration, final String name) { + final List<Attribute> result = new ArrayList<>(); + + for(final Attribute attribute : configuration.getAttributes()) { + final Instance handlerConfiguration = LanguageHelper.lookup((Instance)attribute.getValue(), "handling"); + final String handlerName = LanguageHelper.lookup(handlerConfiguration, "name"); + + if(name.equals(handlerName)) { + result.add(attribute); + } + } + + return result; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/correlation/CorrelationsProducer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/correlation/CorrelationsProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..ca3373bc7903bb03ea6621435957df0377e22d17 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/correlation/CorrelationsProducer.java @@ -0,0 +1,83 @@ +package de.evoal.core.main.ddl.correlation; + +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.properties.PropertiesSpecification; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import de.evoal.core.main.ea.functions.correlation.model.RangedCorrelation; +import de.evoal.core.main.ddl.correlation.el.AstHelper; +import de.evoal.core.main.ddl.el.ElHelper; +import de.evoal.languages.model.el.Call; +import de.evoal.languages.model.el.Expression; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Named; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; + +/** + * Extracts all correlations from the DVL AST. + */ +@ApplicationScoped +@Slf4j +public class CorrelationsProducer { + + private CustomCodec codec; + + private PropertiesSpecification specification; + + @Produces + @ApplicationScoped + public Correlations createCorrelations(final @Named("data-constraints") Collection<Expression> expressions, + final @Named("source-properties-specification") PropertiesSpecification specification, + final CustomCodec codec) { + this.codec = codec; + this.specification = specification; + final Correlations correlations = new Correlations(codec); + + expressions.stream() + .map(ElHelper::findCall) + .filter(Objects::nonNull) + .map(Call.class::cast) + .filter(c -> "connection".equals(((de.evoal.languages.model.ddl.FunctionName)c.getFunction()).getDefinition().getName())) + .map(c -> convert(c)) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(correlations.getCorrelations()::add); + + log.info("Loaded {} correlations.", correlations.getCorrelations().size()); + + return correlations; + } + + private Optional<Correlation> convert(final Call constraint) { + if(constraint.getParameters().size() == 3) { + final Correlation result = new Correlation(); + result.setChromosomeOne(AstHelper.findChromosomeIndex(specification, constraint.getParameters().get(0))); + result.setChromosomeTwo(AstHelper.findChromosomeIndex(specification, constraint.getParameters().get(1))); + result.setCorrelationFactor(ElHelper.findNumber(constraint.getParameters().get(2)).doubleValue()); + result.setCodec(codec); + + return Optional.of(result); + } else if(constraint.getParameters().size() == 5) { + final RangedCorrelation result = new RangedCorrelation(); + result.setChromosomeOne(AstHelper.findChromosomeIndex(specification, constraint.getParameters().get(0))); + result.setChromosomeTwo(AstHelper.findChromosomeIndex(specification, constraint.getParameters().get(2))); + result.setCorrelationFactor(ElHelper.findNumber(constraint.getParameters().get(4)).doubleValue()); + result.setCodec(codec); + + result.setChromosomeOneRange(AstHelper.findRange(constraint.getParameters().get(1))); + result.setChromosomeTwoRange(AstHelper.findRange(constraint.getParameters().get(3))); + + return Optional.of(result); + } else { + log.error("connection constraint has wrong number of parameters: {}", constraint.getParameters().size()); + return Optional.empty(); + } + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/correlation/el/AstHelper.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/correlation/el/AstHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..c0b4c88601856b234808101806e1023b1dcfca9c --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/correlation/el/AstHelper.java @@ -0,0 +1,22 @@ +package de.evoal.core.main.ddl.correlation.el; + +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.core.main.ea.functions.correlation.model.Range; +import de.evoal.core.main.ddl.el.StringSwitch; +import de.evoal.languages.model.el.Expression; + +public final class AstHelper { + private AstHelper() { + } + + public static Range findRange(final Expression expression) { + return new RangeSwitch().doSwitch(expression); + } + + public static int findChromosomeIndex(final PropertiesSpecification specification, final Expression expression) { + final String name = new StringSwitch().doSwitch(expression); + + return specification.indexOf(new PropertySpecification(name)); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/correlation/el/RangeSwitch.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/correlation/el/RangeSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..58fc1b0c7d729304dfc34abda3975adf31cf392b --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/correlation/el/RangeSwitch.java @@ -0,0 +1,121 @@ +package de.evoal.core.main.ddl.correlation.el; + +import de.evoal.core.main.ea.functions.correlation.model.Range; +import de.evoal.core.main.ddl.el.ElHelper; +import de.evoal.languages.model.el.*; +import de.evoal.languages.model.el.util.ELSwitch; + +import java.util.Objects; + +public class RangeSwitch extends ELSwitch<Range> { + @Override + public Range caseOrExpression(final OrExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Range caseXorExpression(final XorExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Range caseAndExpression(final AndExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Range caseNotExpression(final NotExpression object) { + return this.doSwitch(object.getOperand()); + } + + @Override + public Range caseComparisonExpression(final ComparisonExpression object) { + Objects.equals(object.getComparison().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Range caseAddOrSubtractExpression(final AddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Range caseMultiplyDivideModuloExpression(MultiplyDivideModuloExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Range casePowerOfExpression(PowerOfExpression object) { + Objects.isNull(object.getRightOperand()); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Range caseUnaryAddOrSubtractExpression(UnaryAddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getSubExpression()); + } + /* + @Override + public Range caseCallOrLiteralOrReferenceOrParantheses(CallOrLiteralOrReferenceOrParantheses object) { + return super.caseCallOrLiteralOrReferenceOrParantheses(object); + } + */ + + @Override + public Range caseIntegerLiteral(final IntegerLiteral object) { + throw new IllegalStateException("Searching for Range but found a integer literal."); + } + + @Override + public Range caseDoubleLiteral(final DoubleLiteral object) { + throw new IllegalStateException("Searching for range but found a double literal."); + } + + @Override + public Range caseStringLiteral(StringLiteral object) { + throw new IllegalStateException("Searching for Range but found a string literal."); + } + + @Override + public Range caseBooleanLiteral(BooleanLiteral object) { + throw new IllegalStateException("Searching for Range but found a boolean literal."); + } + + @Override + public Range caseCall(final Call object) { + final de.evoal.languages.model.eal.FunctionName calledFunction = (de.evoal.languages.model.eal.FunctionName)object.getFunction(); + if(!"range".equals(calledFunction.getDefinition().getName())) { + throw new IllegalStateException("Searching for range but found: " + calledFunction.getDefinition().getName()); + } + + Objects.equals(object.getParameters().size(), 2); + + final Number lowerBound = ElHelper.findNumber(object.getParameters().get(0)); + final Number upperBound = ElHelper.findNumber(object.getParameters().get(1)); + + final Range result = new Range(); + result.setLower(lowerBound.doubleValue()); + result.setUpper(upperBound.doubleValue()); + + return result; + } + + @Override + public Range caseParantheses(Parantheses object) { + return this.doSwitch(object.getSubExpression()); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/deviation/DeviationProducer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/deviation/DeviationProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..de27c15b3221fbd283e253c8da3fc6176a1279c7 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/deviation/DeviationProducer.java @@ -0,0 +1,67 @@ +package de.evoal.core.main.ddl.deviation; + +import de.evoal.core.main.ddl.deviation.model.Deviation; +import de.evoal.core.main.ddl.deviation.model.Deviations; +import de.evoal.core.main.ddl.el.ElHelper; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import javax.inject.Named; + +import de.evoal.languages.model.el.Call; +import de.evoal.languages.model.el.Expression; +import de.evoal.languages.model.el.FunctionName; +import lombok.extern.slf4j.Slf4j; + +import java.util.Collection; +import java.util.Objects; +import java.util.Optional; + +@ApplicationScoped +@Slf4j +public class DeviationProducer { + private PropertiesSpecification specification; + + @Produces + @ApplicationScoped + public Deviations create( + final @Named("data-constraints") Collection<Expression> expressions, + final CustomCodec codec) { + this.specification = specification; + final Deviations deviations = new Deviations(specification); + + expressions.stream() + .map(ElHelper::findCall) + .filter(Objects::nonNull) + .map(Call.class::cast) + .filter(c -> "standardDeviation".equals(((de.evoal.languages.model.ddl.FunctionName)c.getFunction()).getDefinition().getName())) + .map(c -> convert(c)) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(deviations::add); + + return deviations; + } + + private Optional<Deviation> convert(final Call constraint) { + if(constraint.getParameters().size() != 1) { + log.error("Deviation has more than two parameters. Skipping."); + return Optional.empty(); + } + + final String propertyName = ElHelper.findValueReference(constraint.getParameters().get(0)); + final double deviation = ElHelper.findNumber(constraint.getParameters().get(1)).doubleValue(); + + final PropertySpecification spec = new PropertySpecification(propertyName); + + final Deviation result = new Deviation(); + result.setSpecification(spec); + result.setIndex(specification.indexOf(spec)); + result.setDeviation(deviation); + + return Optional.of(result); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/deviation/model/Deviation.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/deviation/model/Deviation.java new file mode 100644 index 0000000000000000000000000000000000000000..115c44720da024aec5eb29a0fa5c93db67365c93 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/deviation/model/Deviation.java @@ -0,0 +1,22 @@ +package de.evoal.core.main.ddl.deviation.model; + +import de.evoal.core.api.properties.PropertySpecification; +import lombok.Data; + +@Data +public class Deviation { + /** + * The configured deviation value. + */ + private double deviation; + + /** + * Index of the property. + */ + private int index; + + /** + * The property specification of the used property. + */ + private PropertySpecification specification; +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/deviation/model/Deviations.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/deviation/model/Deviations.java new file mode 100644 index 0000000000000000000000000000000000000000..654308b9e9d445f18c9cf87ca13fef613b0cc85f --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/deviation/model/Deviations.java @@ -0,0 +1,33 @@ +package de.evoal.core.main.ddl.deviation.model; + +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class Deviations { + private final PropertiesSpecification specification; + + private List<Optional<Double>> standardDeviations = new ArrayList<>(); + + public Deviations(final PropertiesSpecification specification) { + this.specification = specification; + for(int i = 0; i < specification.size(); ++i) { + standardDeviations.add(Optional.empty()); + } + } + + public void add(final Deviation deviation) { + standardDeviations.set(deviation.getIndex(), Optional.of(deviation.getDeviation())); + } + + public Optional<Double> find(final int propertyIndex) { + return standardDeviations.get(propertyIndex); + } + + public Optional<Double> find(final PropertySpecification spec) { + return standardDeviations.get(specification.indexOf(spec)); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/CallSwitch.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/CallSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..8f44f7185e13d6d87832be3207fa47af9360f83d --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/CallSwitch.java @@ -0,0 +1,84 @@ +package de.evoal.core.main.ddl.el; + +import de.evoal.languages.model.el.*; +import de.evoal.languages.model.el.util.ELSwitch; + +import java.util.Objects; + +public class CallSwitch extends ELSwitch<Call> { + @Override + public Call caseOrExpression(final OrExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Call caseXorExpression(XorExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Call caseAndExpression(AndExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Call caseNotExpression(NotExpression object) { + return this.doSwitch(object.getOperand()); + } + + @Override + public Call caseComparisonExpression(ComparisonExpression object) { + Objects.equals(object.getComparison().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Call caseAddOrSubtractExpression(AddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Call caseMultiplyDivideModuloExpression(MultiplyDivideModuloExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Call casePowerOfExpression(PowerOfExpression object) { + Objects.isNull(object.getRightOperand()); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Call caseUnaryAddOrSubtractExpression(UnaryAddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getSubExpression()); + } + /* + @Override + public String caseCallOrLiteralOrReferenceOrParantheses(CallOrLiteralOrReferenceOrParantheses object) { + return super.caseCallOrLiteralOrReferenceOrParantheses(object); + } + */ + + @Override + public Call caseParantheses(Parantheses object) { + return this.doSwitch(object.getSubExpression()); + } + + public Call caseCall(Call object) { + return object; + } +} \ No newline at end of file diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/ElHelper.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/ElHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..344a04e1b0221d651e9a4181377d57a5e1084fb8 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/ElHelper.java @@ -0,0 +1,25 @@ +package de.evoal.core.main.ddl.el; + +import de.evoal.languages.model.el.Call; +import de.evoal.languages.model.el.Expression; + +public final class ElHelper { + private ElHelper() { + } + + public static Number findNumber(final Expression expression) { + return new NumberSwitch().doSwitch(expression); + } + + public static String findString(final Expression expression) { + return new StringSwitch().doSwitch(expression); + } + + public static String findValueReference(final Expression expression) { + return new ValueReferenceSwitch().doSwitch(expression); + } + + public static Call findCall(final Expression expression) { + return new CallSwitch().doSwitch(expression); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/NumberSwitch.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/NumberSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..18e788f51d4c3a49a77f26c422bbab9bfd254d9d --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/NumberSwitch.java @@ -0,0 +1,119 @@ +package de.evoal.core.main.ddl.el; + +import de.evoal.languages.model.el.*; +import de.evoal.languages.model.el.util.ELSwitch; + +import java.util.Objects; + +public class NumberSwitch extends ELSwitch<Number> { + @Override + public Number caseOrExpression(final OrExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Number caseXorExpression(XorExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Number caseAndExpression(AndExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public Number caseNotExpression(NotExpression object) { + return this.doSwitch(object.getOperand()); + } + + @Override + public Number caseComparisonExpression(ComparisonExpression object) { + Objects.equals(object.getComparison().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Number caseAddOrSubtractExpression(final AddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Number caseMultiplyDivideModuloExpression(MultiplyDivideModuloExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Number casePowerOfExpression(PowerOfExpression object) { + Objects.isNull(object.getRightOperand()); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public Number caseUnaryAddOrSubtractExpression(UnaryAddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + Number number = this.doSwitch(object.getSubExpression()); + + for(final AddOrSubtractOperator aso : object.getOperators()) { + if(AddOrSubtractOperator.ADD.equals(aso)) { + continue; + } + + if(number instanceof Integer) { + number = Math.negateExact(number.intValue()); + } else if(number instanceof Double) { + number = -number.doubleValue(); + } + } + + return number; + } + /* + @Override + public Number caseCallOrLiteralOrReferenceOrParantheses(CallOrLiteralOrReferenceOrParantheses object) { + return super.caseCallOrLiteralOrReferenceOrParantheses(object); + } + */ + + @Override + public Number caseIntegerLiteral(final IntegerLiteral object) { + return object.getValue(); + } + + @Override + public Number caseDoubleLiteral(final DoubleLiteral object) { + return object.getValue(); + } + + @Override + public Number caseStringLiteral(StringLiteral object) { + throw new IllegalStateException("Searching for number but found a string literal."); + } + + @Override + public Number caseBooleanLiteral(BooleanLiteral object) { + throw new IllegalStateException("Searching for number but found a boolean literal."); + } + + @Override + public Number caseCall(final Call object) { + throw new IllegalStateException("Searching for number but found a call."); + } + + @Override + public Number caseParantheses(Parantheses object) { + return this.doSwitch(object.getSubExpression()); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/StringSwitch.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/StringSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..cc010449181148bb422b75e3dddb999e1031383e --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/StringSwitch.java @@ -0,0 +1,105 @@ +package de.evoal.core.main.ddl.el; + +import de.evoal.languages.model.el.*; +import de.evoal.languages.model.el.util.ELSwitch; + +import java.util.Objects; + +public class StringSwitch extends ELSwitch<String> { + @Override + public String caseOrExpression(final OrExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public String caseXorExpression(final XorExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public String caseAndExpression(final AndExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public String caseNotExpression(final NotExpression object) { + return this.doSwitch(object.getOperand()); + } + + @Override + public String caseComparisonExpression(final ComparisonExpression object) { + Objects.equals(object.getComparison().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public String caseAddOrSubtractExpression(AddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public String caseMultiplyDivideModuloExpression(MultiplyDivideModuloExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public String casePowerOfExpression(PowerOfExpression object) { + Objects.isNull(object.getRightOperand()); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public String caseUnaryAddOrSubtractExpression(UnaryAddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getSubExpression()); + } + /* + @Override + public String caseCallOrLiteralOrReferenceOrParantheses(CallOrLiteralOrReferenceOrParantheses object) { + return super.caseCallOrLiteralOrReferenceOrParantheses(object); + } + */ + + @Override + public String caseIntegerLiteral(final IntegerLiteral object) { + throw new IllegalStateException("Searching for String but found a integer literal."); + } + + @Override + public String caseDoubleLiteral(final DoubleLiteral object) { + throw new IllegalStateException("Searching for String but found a double literal."); + } + + @Override + public String caseStringLiteral(StringLiteral object) { + return object.getValue(); + } + + @Override + public String caseBooleanLiteral(BooleanLiteral object) { + throw new IllegalStateException("Searching for String but found a boolean literal."); + } + + @Override + public String caseCall(final Call object) { + throw new IllegalStateException("Searching for String but found a call."); + } + + @Override + public String caseParantheses(Parantheses object) { + return this.doSwitch(object.getSubExpression()); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/ValueReferenceSwitch.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/ValueReferenceSwitch.java new file mode 100644 index 0000000000000000000000000000000000000000..c8f08541c24bf5e3ae2bd8a5e1edc4c7cac2f504 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ddl/el/ValueReferenceSwitch.java @@ -0,0 +1,116 @@ +package de.evoal.core.main.ddl.el; + +import de.evoal.languages.model.eal.DataReference; +import de.evoal.languages.model.el.*; +import de.evoal.languages.model.el.util.ELSwitch; + +import java.util.Objects; + +public class ValueReferenceSwitch extends ELSwitch<String> { + @Override + public String caseOrExpression(final OrExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public String caseXorExpression(XorExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public String caseAndExpression(AndExpression object) { + Objects.equals(object.getSubExpressions().size(), 1); + + return this.doSwitch(object.getSubExpressions().get(0)); + } + + @Override + public String caseNotExpression(NotExpression object) { + return this.doSwitch(object.getOperand()); + } + + @Override + public String caseComparisonExpression(ComparisonExpression object) { + Objects.equals(object.getComparison().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public String caseAddOrSubtractExpression(AddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public String caseMultiplyDivideModuloExpression(MultiplyDivideModuloExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public String casePowerOfExpression(PowerOfExpression object) { + Objects.isNull(object.getRightOperand()); + + return this.doSwitch(object.getLeftOperand()); + } + + @Override + public String caseUnaryAddOrSubtractExpression(UnaryAddOrSubtractExpression object) { + Objects.equals(object.getOperators().size(), 0); + + return this.doSwitch(object.getSubExpression()); + } + /* + @Override + public String caseCallOrLiteralOrReferenceOrParantheses(CallOrLiteralOrReferenceOrParantheses object) { + return super.caseCallOrLiteralOrReferenceOrParantheses(object); + } + */ + + @Override + public String caseIntegerLiteral(final IntegerLiteral object) { + throw new IllegalStateException("Searching for a value reference but found a integer literal."); + } + + @Override + public String caseDoubleLiteral(final DoubleLiteral object) { + throw new IllegalStateException("Searching for a value reference but found a double literal."); + } + + @Override + public String caseStringLiteral(StringLiteral object) { + throw new IllegalStateException("Searching for a value reference but found a string literal."); + } + + @Override + public String caseBooleanLiteral(BooleanLiteral object) { + throw new IllegalStateException("Searching for a value reference but found a boolean literal."); + } + + @Override + public String caseCall(final Call object) { + throw new IllegalStateException("Searching for a value reference but found a call."); + } + + @Override + public String caseParantheses(Parantheses object) { + return this.doSwitch(object.getSubExpression()); + } + + @Override + public String caseValueReference(final ValueReference object) { + if(!(object instanceof DataReference)) { + throw new IllegalStateException("Value reference is not a data reference."); + } + + final DataReference reference = (DataReference)object; + return reference.getDefinition().getName(); + } +} \ No newline at end of file diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/AltererFactory.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/AltererFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..26927ebf0828c35d89b75bf8ec173f6635430291 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/AltererFactory.java @@ -0,0 +1,195 @@ +package de.evoal.core.main.ea.alterer; + +import java.util.function.BiFunction; + +import de.evoal.core.main.ea.alterer.internal.MeanCorrelationAlterer; +import de.evoal.core.main.ea.alterer.crossover.*; +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import de.evoal.languages.model.instance.Instance; +import de.evoal.core.api.utils.LanguageHelper; +import de.evoal.core.main.ea.alterer.mutator.SingleBitFlipCorrelationMutator; +import de.evoal.core.main.ea.alterer.mutator.SingleBitFlipMutator; +import de.evoal.core.main.ea.alterer.mutator.SwapCorrelationMutator; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import io.jenetics.*; +import io.jenetics.util.Mean; +import javax.enterprise.context.ApplicationScoped; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Inject; + +@ApplicationScoped +@Slf4j +public class AltererFactory { + + @Inject + private Correlations correlations; + + @Inject + private BiFunction<Double, Double, Alterer> factory; + + /** + * Creates an alterer based on the heuristic configuration. + * + * Blackboard slots used: + * <ul> + * <li>None</li> + * </ul> + */ + public <G extends Gene<?, G>> Alterer<G, FitnessType> create(final Instance config) { + final String name = LanguageHelper.lookup(config, "name"); + + log.info("Creating alterer with name '{}'.", name); + + switch(name) { + //case "CompositeAlterer": return createCompositeAlterer(config); + case "mean_alterer": return createMeanAlterer(config); + case "correlation_mean_alterer": return (Alterer<G, FitnessType>) createCorrelationMeanAlterer(config); + case "partial_matched_alterer": return createPartiallyMatchedAlterer(config); + case "correlation_partial_matched_alterer": return createCorrelationPartiallyMatchedAlterer(config); + + case "gaussian_mutator": return createGaussianMutator(config); + case "correlation_gaussian_mutator": return createGaussianCorrelationMutator(config); + case "swap_mutator": return createSwapMutator(config); + case "correlation_swap_mutator": return createCorrelationSwapMutator(config); + case "bit_flip_mutator": return createBitFlipMutator(config); + case "correlation_bit_flip_mutator": return createCorrelationBitFlipMutator(config); + +// case "IntermediateCrossover": return createIntermediateCrossover(config); + case "line_crossover": return createLineCrossover(config); + case "correlation_line_crossover": return createCorrelationLineCrossover(config); + case "multi_point_crossover": return createMultiPointCrossover(config); + case "correlation_multi_point_crossover": return createCorrelationMultiPointCrossover(config); + case "single_point_crossover": return createSinglePointCrossover(config); + case "correlation_single_point_crossover": return createCorrelationSinglePointCrossover(config); + case "uniform_crossover": return createUniformCrossover(config); + case "correlation_uniform_crossover": return createCorrelationUniformCrossover(config); + } + throw new IllegalStateException("Selector '" + name + "' is unknown."); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createUniformCrossover(final Instance config) { + final Double crossoverProbability = LanguageHelper.lookup(config, "crossover_probability"); + final Double swapProbability = LanguageHelper.lookup(config,"swap_probability"); + + return new UniformCrossover<>(crossoverProbability, swapProbability); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createCorrelationUniformCrossover(final Instance config) { + final Double crossoverProbability = LanguageHelper.lookup(config, "crossover_probability"); + final Double swapProbability = LanguageHelper.lookup(config,"swap_probability"); + + return new UniformCorrelationCrossover(crossoverProbability, swapProbability, correlations); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createBitFlipMutator (final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + + return new SingleBitFlipMutator(probability); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createCorrelationBitFlipMutator (final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + final Double threshold = LanguageHelper.lookup(config, "threshold"); + + return new SingleBitFlipCorrelationMutator(probability, threshold, correlations); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createSwapMutator (final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + + return new SwapMutator<>(probability); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createCorrelationSwapMutator (final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + final Double threshold = LanguageHelper.lookup(config, "threshold"); + + return new SwapCorrelationMutator<>(probability, threshold, correlations); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createSinglePointCrossover(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + + return new SinglePointCrossover<>(probability); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createCorrelationSinglePointCrossover(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + + return new SinglePointCorrelationCrossover(probability, correlations); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createPartiallyMatchedAlterer(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + + return (Alterer<G, FitnessType>) new PartiallyMatchedCrossover(probability); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createCorrelationPartiallyMatchedAlterer(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + + return (Alterer<G, FitnessType>) new PartiallyMatchedCorrelationCrossover<G, FitnessType>(probability, correlations); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createMultiPointCrossover(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + final Integer count = LanguageHelper.lookup(config, "count"); + + return new MultiPointCrossover<>(probability, count); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createCorrelationMultiPointCrossover(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + final Integer count = LanguageHelper.lookup(config, "count"); + + return new MultiPointCorrelationCrossover<>(probability, count, correlations); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createMeanAlterer(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + + return (Alterer<G, FitnessType>) new MeanAlterer(probability); + } + + private <G extends NumericGene<?, G> & Mean<G>> Alterer<G, FitnessType> createCorrelationMeanAlterer(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + + return (Alterer<G, FitnessType>) new MeanCorrelationAlterer<G, FitnessType>(probability, correlations); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createLineCrossover(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + final Double position = LanguageHelper.lookup(config, "position"); + + return new LineCrossover(probability, position); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createCorrelationLineCrossover(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + final Double position = LanguageHelper.lookup(config, "position"); + + return new LineCorrelationCrossover(probability, position, correlations); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createIntermediateCrossover(final Instance config) { + throw new IllegalStateException("Unsupported alterer."); + } + + private <G extends Gene<?,G>> Alterer<G, FitnessType> createGaussianMutator(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + + return new GaussianMutator(probability); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createGaussianCorrelationMutator(final Instance config) { + final Double probability = LanguageHelper.lookup(config, "probability"); + final Double threshold = LanguageHelper.lookup(config, "threshold"); + + return factory.apply(probability, threshold); + } + + private <G extends Gene<?, G>> Alterer<G, FitnessType> createCompositeAlterer(final Instance config) { + throw new IllegalStateException("Unsupported alterer."); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/CorrelationCrossover.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/CorrelationCrossover.java new file mode 100644 index 0000000000000000000000000000000000000000..c5748e3c755ac5c2af2393cc03f3dff2f0ba8143 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/CorrelationCrossover.java @@ -0,0 +1,106 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.*; +import io.jenetics.util.MSeq; +import io.jenetics.util.RandomRegistry; + +import java.util.random.RandomGenerator; + +import static java.lang.Math.min; + +public abstract class CorrelationCrossover< + G extends Gene<?, G>, + C extends Comparable<? super C>, + M extends CorrelationCrossoverMemento<M> + > + extends Recombinator<G, C> { + + private final Correlations<G> correlations; + + /** + * Constructs an alterer with a given recombination probability. + * + * @param probability the recombination probability + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]} + */ + protected CorrelationCrossover(final double probability, final Correlations correlations) { + super(probability, 2); + + this.correlations = correlations; + } + + @Override + protected final int recombine( + final MSeq<Phenotype<G, C>> population, + final int[] individuals, + final long generation + ) { + assert individuals.length == 2 : "Required order of 2"; + final RandomGenerator random = RandomRegistry.random(); + + final var pt1 = population.get(individuals[0]); + final var pt2 = population.get(individuals[1]); + final var gt1 = pt1.genotype(); + final var gt2 = pt2.genotype(); + + //Choosing the Chromosome index for crossover. + final int chIndex = random.nextInt(min(gt1.length(), gt2.length())); + final int rootIndex = correlations.findCorrelationRoot(gt1, chIndex); + + final var c1 = MSeq.of(gt1); + final var c2 = MSeq.of(gt2); + + final var memento = newCrossoverMemento(); + + final var correlationOrder = crossover(gt1, gt2, c1, c2, rootIndex, memento); + + //Creating two new Phenotypes and exchanging it with the old. + population.set( + individuals[0], + Phenotype.of(Genotype.of(c1), generation) + ); + population.set( + individuals[1], + Phenotype.of(Genotype.of(c2), generation) + ); + + return correlationOrder; + } + + private int crossover(final Genotype<G> gt1, final Genotype<G> gt2, final MSeq<Chromosome<G>> c1, final MSeq<Chromosome<G>> c2, final int chIndex, final M memento) { + final var genes1 = MSeq.of(c1.get(chIndex)); + final var genes2 = MSeq.of(c2.get(chIndex)); + + int correlationOrder = crossover(memento, genes1, genes2); + + c1.set(chIndex, c1.get(chIndex).newInstance(genes1.toISeq())); + c2.set(chIndex, c2.get(chIndex).newInstance(genes2.toISeq())); + + for(final Correlation correlation : correlations.find(gt1, chIndex)) { + final int targetIndex = correlation.getChromosomeTwo(); + + correlationOrder += crossover(gt1, gt2, c1, c2, targetIndex, newCrossoverMemento().apply(memento, correlation)); // sum up correlations + } + + return correlationOrder; + } + + /** + * @return A valid and initialized crossover memento. + */ + protected abstract M newCrossoverMemento(); + + /** + * Template method which performs the crossover. The arguments given are + * mutable non null arrays of the same length. + * + * @oaram memento the crossover memento to be applied + * @param that the genes of the first chromosome + * @param other the genes of the other chromosome + * @return the number of altered genes + */ + protected abstract int crossover(final M memento, final MSeq<G> that, final MSeq<G> other); +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/CorrelationCrossoverMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/CorrelationCrossoverMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..929b221040152b4265d780b6016b9ce2b77f2995 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/CorrelationCrossoverMemento.java @@ -0,0 +1,11 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; + +public interface CorrelationCrossoverMemento<T extends CorrelationCrossoverMemento<T>> { + + /** + * Applies the given memento with according to the correlation to this memento. + */ + public T apply(final T memento, final Correlation correlation); +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/LineCorrelationCrossover.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/LineCorrelationCrossover.java new file mode 100644 index 0000000000000000000000000000000000000000..a044d67736bd3cea7c90ed15c8be74f51f504ab4 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/LineCorrelationCrossover.java @@ -0,0 +1,82 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.api.utils.Requirements; +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.NumericGene; +import io.jenetics.util.MSeq; + +import static java.lang.Math.min; +import static java.lang.String.format; + +public class LineCorrelationCrossover< + G extends NumericGene<?, G>, + C extends Comparable<? super C> + > extends CorrelationCrossover<G, C, LineCorrelationCrossoverMemento> { + private final double _p; + + /** + * Creates a new linear-crossover with the given recombination + * probability and the line-scaling factor <em>p</em>. + * + * @param probability the recombination probability. + * @param p defines the possible location of the recombined chromosomes. If + * <em>p</em> = 0 then the children will be located along the line + * within the hypercube between the two points. If <em>p</em> > 0 + * then the children may be located anywhere on the line, even + * somewhat outside of the hypercube. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]} or if {@code p} is smaller then zero + */ + public LineCorrelationCrossover(final double probability, final double p, final Correlations correlations) { + super(probability, correlations); + _p = Requirements.nonNegative(p); + } + + /** + * Creates a new linear-crossover with the given recombination + * probability. The parameter <em>p</em> is set to zero, which restricts the + * recombined chromosomes within the hypercube of the selected chromosomes + * (vectors). + * + * @param probability the recombination probability. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]} + */ + public LineCorrelationCrossover(final double probability, final Correlations correlations) { + this(probability, 0, correlations); + } + + @Override + protected int crossover(final LineCorrelationCrossoverMemento memento, final MSeq<G> v, final MSeq<G> w) { + final double min = v.get(0).min().doubleValue(); + final double max = v.get(0).max().doubleValue(); + + + boolean changed = false; + for (int i = 0, n = min(v.length(), w.length()); i < n; ++i) { + final double vi = v.get(i).doubleValue(); + final double wi = w.get(i).doubleValue(); + + final double t = memento.getA() * vi + (1 - memento.getA()) * wi; + final double s = memento.getB() * wi + (1 - memento.getB()) * vi; + + if (t >= min && s >= min && t < max && s < max) { + v.set(i, v.get(i).newInstance(t)); + w.set(i, w.get(i).newInstance(s)); + changed = true; + } + } + + return changed ? 2 : 0; + } + + @Override + public String toString() { + return format("%s[p=%f]", getClass().getSimpleName(), _probability); + } + + @Override + protected LineCorrelationCrossoverMemento newCrossoverMemento() { + return new LineCorrelationCrossoverMemento(_p); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/LineCorrelationCrossoverMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/LineCorrelationCrossoverMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..3ddd0104a2322b2b0a7ace35e6faccf74dd82b05 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/LineCorrelationCrossoverMemento.java @@ -0,0 +1,24 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import io.jenetics.util.RandomRegistry; +import lombok.Data; + +import java.util.random.RandomGenerator; + +@Data +public class LineCorrelationCrossoverMemento implements CorrelationCrossoverMemento<LineCorrelationCrossoverMemento> { + private double a; + private double b; + + public LineCorrelationCrossoverMemento(final double probability) { + final RandomGenerator random = RandomRegistry.random(); + random.nextDouble(-probability, 1 + probability); + random.nextDouble(-probability, 1 + probability); + } + + @Override + public LineCorrelationCrossoverMemento apply(LineCorrelationCrossoverMemento memento, Correlation correlation) { + return this; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/MultiPointCorrelationCrossover.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/MultiPointCorrelationCrossover.java new file mode 100644 index 0000000000000000000000000000000000000000..b829f6dfab02fcd31a5fda7c26fd72aa88ba9beb --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/MultiPointCorrelationCrossover.java @@ -0,0 +1,114 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.Gene; +import io.jenetics.util.MSeq; + +import static java.lang.Math.min; +import static java.lang.String.format; + +public class MultiPointCorrelationCrossover< + G extends Gene<?, G>, + C extends Comparable<? super C>, + M extends MultiPointCorrelationCrossoverMemento<M> + > extends CorrelationCrossover<G, C, M> { + private final int _n; + + /** + * Create a new crossover instance. + * + * @param probability the recombination probability. + * @param n the number of crossover points. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]} or {@code n < 1}. + */ + public MultiPointCorrelationCrossover(final double probability, final int n, final Correlations correlations) { + super(probability, correlations); + if (n < 1) { + throw new IllegalArgumentException(format( + "n must be at least 1 but was %d.", n + )); + } + _n = n; + } + + /** + * Create a new crossover instance with two crossover points. + * + * @param probability the recombination probability. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]}. + */ + public MultiPointCorrelationCrossover(final double probability, final Correlations correlations) { + this(probability, 2, correlations); + } + + /** + * Create a new crossover instance with default crossover probability of + * 0.05. + * + * @param n the number of crossover points. + * @throws IllegalArgumentException if {@code n < 1}. + */ + public MultiPointCorrelationCrossover(final int n, final Correlations correlations) { + this(0.05, n, correlations); + } + + /** + * Create a new crossover instance with two crossover points and crossover + * probability 0.05. + */ + public MultiPointCorrelationCrossover(final Correlations correlations) { + this(0.05, 2, correlations); + } + + /** + * Return the number of crossover points. + * + * @return the number of crossover points. + */ + public int crossoverPointCount() { + return _n; + } + + @Override + protected int crossover(final M memento, final MSeq<G> that, final MSeq<G> other) { + assert that.length() == other.length(); + + final int[] points = memento.getCrossoverPoints(that.length(), other.length(), _n); + + crossover(that, other, points); + return 2; + } + + @Override + protected M newCrossoverMemento() { + return (M)new MultiPointCorrelationCrossoverMemento(); + } + + // Package private for testing purpose. + static <T> void crossover( + final MSeq<T> that, + final MSeq<T> other, + final int[] indexes + ) { + + for (int i = 0; i < indexes.length - 1; i += 2) { + final int start = indexes[i]; + final int end = indexes[i + 1]; + that.swap(start, end, other, start); + } + if (indexes.length%2 == 1) { + final int index = indexes[indexes.length - 1]; + that.swap(index, min(that.length(), other.length()), other, index); + } + } + + @Override + public String toString() { + return format( + "%s[p=%f, n=%d]", + getClass().getSimpleName(), _probability, _n + ); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/MultiPointCorrelationCrossoverMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/MultiPointCorrelationCrossoverMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..1bdf0e05649590e1b82e6e4b5eb5b704cb28cffe --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/MultiPointCorrelationCrossoverMemento.java @@ -0,0 +1,30 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import io.jenetics.internal.math.Subset; +import io.jenetics.util.RandomRegistry; + +import java.util.random.RandomGenerator; + +import static java.lang.Math.min; + +public class MultiPointCorrelationCrossoverMemento<T extends MultiPointCorrelationCrossoverMemento<T>> implements CorrelationCrossoverMemento<T> { + private int[] points = null; + + public int[] getCrossoverPoints(final int thatLength, final int otherLength, final int count) { + if(points == null) { + final int n = min(thatLength, otherLength); + final int k = min(n, count); + + final RandomGenerator random = RandomRegistry.random(); + points = k > 0 ? Subset.next(n, k, random) : new int[0]; + } + + return points; + } + + @Override + public T apply(final T memento, final Correlation correlation) { + return (T)this; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/PartiallyMatchedCorrelationCrossover.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/PartiallyMatchedCorrelationCrossover.java new file mode 100644 index 0000000000000000000000000000000000000000..66977b3bdcc2b8a678711c9e6ffd607ab28c841e --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/PartiallyMatchedCorrelationCrossover.java @@ -0,0 +1,73 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.EnumGene; +import io.jenetics.util.MSeq; + +import static java.lang.String.format; + +public class PartiallyMatchedCorrelationCrossover< + T, + C extends Comparable<? super C> +> extends CorrelationCrossover<EnumGene<T>, C, PartiallyMatchedCorrelationCrossoverMemento> +{ + + public PartiallyMatchedCorrelationCrossover(final double probability, final Correlations correlations) { + super(probability, correlations); + } + + @Override + protected PartiallyMatchedCorrelationCrossoverMemento newCrossoverMemento() { + return new PartiallyMatchedCorrelationCrossoverMemento(); + } + + @Override + protected int crossover( + final PartiallyMatchedCorrelationCrossoverMemento memento, + final MSeq<EnumGene<T>> that, + final MSeq<EnumGene<T>> other + ) { + if (that.length() != other.length()) { + throw new IllegalArgumentException(format( + "Required chromosomes with same length: %s != %s", + that.length(), other.length() + )); + } + + if (that.length() >= 2) { + final int[] points = memento.getCrossoverPoints(that.length()); + + that.swap(points[0], points[1], other, points[0]); + repair(that, other, points[0], points[1]); + repair(other, that, points[0], points[1]); + } + + return 1; + } + + private static <T> void repair( + final MSeq<T> that, final MSeq<T> other, + final int begin, final int end + ) { + for (int i = 0; i < begin; ++i) { + int index = that.indexOf(that.get(i), begin, end); + while (index != -1) { + that.set(i, other.get(index)); + index = that.indexOf(that.get(i), begin, end); + } + } + for (int i = end, n = that.length(); i < n; ++i) { + int index = that.indexOf(that.get(i), begin, end); + while (index != -1) { + that.set(i, other.get(index)); + index = that.indexOf(that.get(i), begin, end); + } + } + } + + @Override + public String toString() { + return format("%s[p=%f]", getClass().getSimpleName(), _probability); + } + +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/PartiallyMatchedCorrelationCrossoverMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/PartiallyMatchedCorrelationCrossoverMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..84615c0a4373c413a7956bc8fc0ba07c46aa7d9f --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/PartiallyMatchedCorrelationCrossoverMemento.java @@ -0,0 +1,28 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import io.jenetics.internal.math.Subset; +import io.jenetics.util.RandomRegistry; + +import java.util.Random; +import java.util.random.RandomGenerator; + +import static java.lang.Math.min; + +public class PartiallyMatchedCorrelationCrossoverMemento implements CorrelationCrossoverMemento<PartiallyMatchedCorrelationCrossoverMemento> { + private int[] points = null; + + public int[] getCrossoverPoints(final int thatLength) { + if(points == null) { + final RandomGenerator random = RandomRegistry.random(); + points = Subset.next(thatLength, 2, random); + } + + return points; + } + + @Override + public PartiallyMatchedCorrelationCrossoverMemento apply(PartiallyMatchedCorrelationCrossoverMemento memento, Correlation correlation) { + return this; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/SinglePointCorrelationCrossover.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/SinglePointCorrelationCrossover.java new file mode 100644 index 0000000000000000000000000000000000000000..42957f149aebabf7745ea41b361befadf82547a8 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/SinglePointCorrelationCrossover.java @@ -0,0 +1,57 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.Gene; +import io.jenetics.util.MSeq; + + +import static java.lang.String.format; + +public class SinglePointCorrelationCrossover< + A, + G extends Gene<A, G>, + C extends Comparable<? super C> +> extends MultiPointCorrelationCrossover<G, C, SinglePointCorrelationCrossoverMemento> { + + /** + * Constructs an alterer with a given recombination probability. + * + * @param probability the crossover probability. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]}. + */ + public SinglePointCorrelationCrossover(final double probability, final Correlations correlations) { + super(probability, 1, correlations); + } + + @Override + protected int crossover(final SinglePointCorrelationCrossoverMemento memento, final MSeq<G> that, final MSeq<G> other) { + final int index = memento.getIndex(that.length(), other.length()); + + if(SinglePointCorrelationCrossoverMemento.BetterChromsome.FIRST.equals(memento.getSelect())) { + copy(other, that, index, that.length() - index, index); + } else { + copy(that, other, index, that.length() - index, index); + } + + return that.length() - index; + } + + private void copy(final MSeq<G> source, final MSeq<G> target, final int start, int size, int index) { + for(int i = 0; i < size; ++i) { + final G gene = source.get(start + i); + final A allele = gene.allele(); + target.set(index + i, gene.newInstance(allele )); + } + } + + @Override + protected SinglePointCorrelationCrossoverMemento newCrossoverMemento() { + return new SinglePointCorrelationCrossoverMemento(); + } + + @Override + public String toString() { + return format("%s[p=%f]", getClass().getSimpleName(), _probability); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/SinglePointCorrelationCrossoverMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/SinglePointCorrelationCrossoverMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..53ee1d9aff828f5a989d937594e441a1db9afb2a --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/SinglePointCorrelationCrossoverMemento.java @@ -0,0 +1,53 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import io.jenetics.util.RandomRegistry; +import lombok.Getter; +import lombok.Setter; + +import java.util.Random; +import java.util.random.RandomGenerator; + +import static java.lang.Math.min; + +public class SinglePointCorrelationCrossoverMemento extends MultiPointCorrelationCrossoverMemento<SinglePointCorrelationCrossoverMemento> { + public enum BetterChromsome { + FIRST, + SECOND + } + + private int index = -1; + + @Getter @Setter + private BetterChromsome select = BetterChromsome.FIRST; + + public int getIndex(final int thatLength, final int otherLength) { + if(index == -1) { + final RandomGenerator random = RandomRegistry.random(); + + index = random.nextInt(min(thatLength, otherLength)); + } + + return index; + } + + + @Override + public SinglePointCorrelationCrossoverMemento apply(final SinglePointCorrelationCrossoverMemento other, final Correlation correlation) { + final double correlationValue = correlation.getCorrelationFactor(); + final SinglePointCorrelationCrossoverMemento result = new SinglePointCorrelationCrossoverMemento(); + result.index = other.index; + + if(BetterChromsome.FIRST.equals(other.select) && correlationValue >= 0) { + result.select = BetterChromsome.FIRST; + } else if(BetterChromsome.FIRST.equals(other.select) && correlationValue < 0) { + result.select = BetterChromsome.SECOND; + } else if(BetterChromsome.SECOND.equals(other.select) && correlationValue >= 0) { + result.select = BetterChromsome.SECOND; + } else if(BetterChromsome.SECOND.equals(other.select) && correlationValue < 0) { + result.select = BetterChromsome.FIRST; + } + + return result; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/UniformCorrelationCrossover.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/UniformCorrelationCrossover.java new file mode 100644 index 0000000000000000000000000000000000000000..18a6ddd84dca859e95ca58f1bfd3f17ecdf96a79 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/UniformCorrelationCrossover.java @@ -0,0 +1,68 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.api.utils.Requirements; +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.Gene; +import io.jenetics.util.MSeq; + +import static java.lang.Math.min; + +public class UniformCorrelationCrossover< + A, + G extends Gene<A, G>, + C extends Comparable<? super C> + > extends CorrelationCrossover<G, C, UniformCorrelationCrossoverMemento> { + private final double _swapProbability; + + /** + * Create a new universal crossover instance. + * + * @param crossoverProbability the recombination probability as defined in + * {@link CorrelationCrossover#CorrelationCrossover(double, Correlations)} . This is the probability that + * a given individual is selected for crossover. + * @param swapProbability the probability for swapping a given gene of + * a chromosome + * @throws IllegalArgumentException if the probabilities are not in the + * valid range of {@code [0, 1]} + */ + public UniformCorrelationCrossover( + final double crossoverProbability, + final double swapProbability, + final Correlations correlations + ) { + super(crossoverProbability, correlations); + + Requirements.requireProbability(swapProbability); + _swapProbability = swapProbability; + } + + @Override + protected UniformCorrelationCrossoverMemento newCrossoverMemento() { + return new UniformCorrelationCrossoverMemento(); + } + + @Override + protected int crossover(final UniformCorrelationCrossoverMemento memento, final MSeq<G> that, final MSeq<G> other) { + final int length = min(that.length(), other.length()); + + final int [] indices = memento.getCrossoverPoints(length, _swapProbability); + + for(int index : indices) { + if (UniformCorrelationCrossoverMemento.BetterChromsome.FIRST.equals(memento.getSelect())) { + copy(other, that, index); + } else { + copy(that, other, index); + } + } + + return indices.length; + } + + private void copy(final MSeq<G> source, final MSeq<G> target, final int index) { + final G gene = source.get(index); + final A allele = gene.allele(); + final G copy = gene.newInstance(allele); + + target.set(index, copy); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/UniformCorrelationCrossoverMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/UniformCorrelationCrossoverMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..4fe2316e26afa1ca7f113f21aa3e0663c3e626e8 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/crossover/UniformCorrelationCrossoverMemento.java @@ -0,0 +1,53 @@ +package de.evoal.core.main.ea.alterer.crossover; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import io.jenetics.util.RandomRegistry; +import lombok.Getter; +import lombok.Setter; + +import java.util.random.RandomGenerator; + +import static io.jenetics.internal.math.Randoms.indexes; + +public class UniformCorrelationCrossoverMemento implements CorrelationCrossoverMemento<UniformCorrelationCrossoverMemento> { + + public enum BetterChromsome { + FIRST, + SECOND + } + + private int[] points = null; + + @Getter + @Setter + private BetterChromsome select = BetterChromsome.FIRST; + + public int[] getCrossoverPoints(final int thatLength, final double probability) { + if(points == null) { + final RandomGenerator random = RandomRegistry.random(); + points = indexes(random, thatLength, probability).toArray(); + } + + return points; + } + + + @Override + public UniformCorrelationCrossoverMemento apply(final UniformCorrelationCrossoverMemento other, final Correlation correlation) { + final double correlationValue = correlation.getCorrelationFactor(); + final UniformCorrelationCrossoverMemento result = new UniformCorrelationCrossoverMemento(); + result.points = other.points; + + if(UniformCorrelationCrossoverMemento.BetterChromsome.FIRST.equals(other.select) && correlationValue >= 0) { + result.select = UniformCorrelationCrossoverMemento.BetterChromsome.FIRST; + } else if(UniformCorrelationCrossoverMemento.BetterChromsome.FIRST.equals(other.select) && correlationValue < 0) { + result.select = UniformCorrelationCrossoverMemento.BetterChromsome.SECOND; + } else if(UniformCorrelationCrossoverMemento.BetterChromsome.SECOND.equals(other.select) && correlationValue >= 0) { + result.select = UniformCorrelationCrossoverMemento.BetterChromsome.SECOND; + } else if(UniformCorrelationCrossoverMemento.BetterChromsome.SECOND.equals(other.select) && correlationValue < 0) { + result.select = UniformCorrelationCrossoverMemento.BetterChromsome.FIRST; + } + + return result; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/AbstractCorrelationAlterer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/AbstractCorrelationAlterer.java new file mode 100644 index 0000000000000000000000000000000000000000..ec6b6646247e49cf6e1a2c0d348e14ab8d452d48 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/AbstractCorrelationAlterer.java @@ -0,0 +1,21 @@ +package de.evoal.core.main.ea.alterer.internal; + +import io.jenetics.AbstractAlterer; +import io.jenetics.Gene; + +public abstract class AbstractCorrelationAlterer< + G extends Gene<?, G>, + C extends Comparable<? super C> +> + extends AbstractAlterer<G, C> { + /** + * Constructs an alterer with a given recombination probability. + * + * @param probability The recombination probability. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]}. + */ + protected AbstractCorrelationAlterer(double probability) { + super(probability); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/CorrelationCombineAlterer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/CorrelationCombineAlterer.java new file mode 100644 index 0000000000000000000000000000000000000000..0ce3b1c205171223b18207effc725772c8de727c --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/CorrelationCombineAlterer.java @@ -0,0 +1,137 @@ +package de.evoal.core.main.ea.alterer.internal; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.*; +import io.jenetics.util.BaseSeq; +import io.jenetics.util.MSeq; +import io.jenetics.util.RandomRegistry; + +import java.util.function.BinaryOperator; +import java.util.random.RandomGenerator; + +import static java.lang.Math.min; +import static java.lang.String.format; +import static java.util.Objects.requireNonNull; + +public abstract class CorrelationCombineAlterer< + G extends NumericGene<?, G>, + C extends Comparable<? super C> + > + extends CorrelationRecombinator<G, C> +{ + + private final BinaryOperator<G> _combiner; + private final Correlations<G> correlations; + + /** + * Create a new combiner alterer with the given arguments. + * + * @param combiner the function used for combining two genes + * @param probability The recombination probability. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]} + * @throws NullPointerException if the given {@code combiner} is {@code null} + */ + public CorrelationCombineAlterer( + final BinaryOperator<G> combiner, + final double probability, + final Correlations correlations + ) { + super(probability, 2); + _combiner = requireNonNull(combiner); + + this.correlations = correlations; + } + + /** + * Create a new combiner alterer with the given arguments. + * + * @param combiner the function used for combining two genes + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]} + * @throws NullPointerException if the given {@code combiner} is {@code null} + */ + public CorrelationCombineAlterer(final BinaryOperator<G> combiner, final Correlations correlations) { + this(combiner, DEFAULT_ALTER_PROBABILITY, correlations ); + } + + /** + * Return the combiner function, used by {@code this} alterer. + * + * @return the combiner function, used by {@code this} alterer + */ + public BinaryOperator<G> combiner() { + return _combiner; + } + + @Override + protected int recombine( + final MSeq<Phenotype<G, C>> population, + final int[] individuals, + final long generation + ) { + final RandomGenerator random = RandomRegistry.random(); + + final Phenotype<G, C> pt1 = population.get(individuals[0]); + final Phenotype<G, C> pt2 = population.get(individuals[1]); + final Genotype<G> gt1 = pt1.genotype(); + final Genotype<G> gt2 = pt2.genotype(); + + // choosing a random Chromosome index for crossover. + final int randomIndex = random.nextInt(min(gt1.length(), gt2.length())); + + // track the correlations to the first index to handle + final int rootIndex = correlations.findCorrelationRoot(gt1, randomIndex); + + return recombine(population, individuals[0], generation, gt1, gt2, rootIndex); + } + + private int recombine(final MSeq<Phenotype<G, C>> population, final int individual, final long generation, final Genotype<G> gt1, final Genotype<G> gt2, final int index) { + final MSeq<Chromosome<G>> c1 = MSeq.of(gt1); + final Chromosome<G> chromosome1 = c1.get(index); + final double chromosomeValue1 = getValue(gt1, index); + + // Calculate the mean value of the gene array. + final MSeq<G> mean = combine(chromosome1, gt2.get(index), _combiner); + + c1.set(index, c1.get(index).newInstance(mean.toISeq())); + population.set(individual, Phenotype.of(Genotype.of(c1), generation)); + + int result = 1; + for(final Correlation correlation : correlations.find(gt1, index)) { + final int index2 = correlation.getChromosomeTwo(); + final double chromosomeValue2 = getValue(gt1, index2); + + if(!correlation.matchesTarget(index2, chromosomeValue2)) { + continue; + } + + result += recombine(population, individual, generation, gt1, gt2, index2); + } + + return result; + } + + private static <G extends Gene<?, G>> + MSeq<G> combine( + final BaseSeq<G> a, + final BaseSeq<G> b, + final BinaryOperator<G> combiner + ) { + final MSeq<G> result = MSeq.ofLength(a.length()); + for (int i = a.length(); --i >= 0;) { + result.set(i, combiner.apply(a.get(i), b.get(i))); + } + return result; + } + + private double getValue(final Genotype<G> genotype, final int index) { + return genotype.get(index).gene().doubleValue(); + } + + @Override + public String toString() { + return format("%s[p=%f]", getClass().getSimpleName(), _probability); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/CorrelationRecombinator.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/CorrelationRecombinator.java new file mode 100644 index 0000000000000000000000000000000000000000..0b05bb86bd62a4181114209d0f0bb5d6f41f9885 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/CorrelationRecombinator.java @@ -0,0 +1,113 @@ +package de.evoal.core.main.ea.alterer.internal; + +import io.jenetics.AltererResult; +import io.jenetics.Gene; +import io.jenetics.Phenotype; +import io.jenetics.internal.math.Subset; +import io.jenetics.util.MSeq; +import io.jenetics.util.RandomRegistry; +import io.jenetics.util.Seq; + +import java.util.random.RandomGenerator; + +import static io.jenetics.internal.math.Randoms.indexes; +import static java.lang.String.format; + +public abstract class CorrelationRecombinator< + G extends Gene<?, G>, + C extends Comparable<? super C> + > + extends AbstractCorrelationAlterer<G, C> { + private final int _order; + + /** + * Constructs an alterer with a given recombination probability. + * + * @param probability The recombination probability. + * @param order the number of individuals involved in the + * {@link #recombine(MSeq, int[], long)} step + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]} or the given {@code order} is + * smaller than two. + */ + protected CorrelationRecombinator(final double probability, final int order) { + super(probability); + if (order < 2) { + throw new IllegalArgumentException(format( + "Order must be greater than one, but was %d.", order + )); + } + _order = order; + } + + /** + * Return the number of individuals involved in the + * {@link #recombine(MSeq, int[], long)} step. + * + * @return the number of individuals involved in the recombination step. + */ + public int order() { + return _order; + } + + @Override + public AltererResult<G, C> alter( + final Seq<Phenotype<G, C>> population, + final long generation + ) { + final AltererResult<G, C> result; + if (population.size() >= 2) { + final RandomGenerator random = RandomRegistry.random(); + final int order = Math.min(_order, population.size()); + + final MSeq<Phenotype<G, C>> pop = MSeq.of(population); + final int count = indexes(random, population.size(), _probability) + .mapToObj(i -> individuals(i, population.size(), order, random)) + .mapToInt(ind -> recombine(pop, ind, generation)) + .sum(); + + result = new AltererResult(pop.toISeq(), count); + } else { + result = new AltererResult(population.asISeq()); + } + + return result; + } + + static int[] individuals( + final int index, + final int size, + final int order, + final RandomGenerator random + ) { + final int[] ind = Subset.next(size, order, random); + + // Find the correct slot for the "master" individual. + // This prevents duplicate index entries. + int i = 0; + while (ind[i] < index && i < ind.length - 1) { + ++i; + } + ind[i] = index; + + return ind; + } + + /** + * Recombination template method. This method is called 0 to n times. It is + * guaranteed that this method is only called by one thread. + * + * @param population the population to recombine + * @param individuals the array with the indexes of the individuals which + * are involved in the <i>recombination</i> step. The length of the + * array is {@link #order()}. The first individual is the + * <i>primary</i> individual. + * @param generation the current generation. + * @return the number of genes that has been altered. + */ + protected abstract int recombine( + final MSeq<Phenotype<G, C>> population, + final int[] individuals, + final long generation + ); +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/MeanCorrelationAlterer.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/MeanCorrelationAlterer.java new file mode 100644 index 0000000000000000000000000000000000000000..d8f44559188c7e4ffb59af2fd2d5be47c32c025b --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/internal/MeanCorrelationAlterer.java @@ -0,0 +1,31 @@ +package de.evoal.core.main.ea.alterer.internal; + +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.*; +import io.jenetics.util.Mean; + +public class MeanCorrelationAlterer< + G extends NumericGene<?, G> & Mean<G>, + C extends Comparable<? super C> +> extends CorrelationCombineAlterer<G, C> { + + /** + * Constructs an alterer with a given recombination probability. + * + * @param probability the crossover probability. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]}. + */ + public MeanCorrelationAlterer(final double probability, final Correlations correlations) { + super((x,y) -> (G)((Mean)x).mean(y), probability, correlations); + } + + /** + * Create a new alterer with alter probability of {@code 0.05}. + */ + public MeanCorrelationAlterer(final Correlations correlations) { + this(0.05, correlations); + } + +} + diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/CorrelationMutator.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/CorrelationMutator.java new file mode 100644 index 0000000000000000000000000000000000000000..186e7b94c4b413e55415bd43c0f8547cbc320ab0 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/CorrelationMutator.java @@ -0,0 +1,220 @@ +package de.evoal.core.main.ea.alterer.mutator; + +import de.evoal.core.main.ea.alterer.internal.AbstractCorrelationAlterer; +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.*; +import io.jenetics.internal.math.Probabilities; +import io.jenetics.util.ISeq; +import io.jenetics.util.RandomRegistry; +import io.jenetics.util.Seq; + +import java.util.ArrayList; +import java.util.List; +import java.util.random.RandomGenerator; + +import static java.lang.Math.pow; +import static java.lang.String.format; + +public abstract class CorrelationMutator< + G extends Gene<?, G>, + C extends Comparable<? super C>, + CC extends CorrelationMutatorMemento +> + extends AbstractCorrelationAlterer<G, C> { + + protected final double threshold; + private final Correlations<G> correlations; + + /** + * Construct a Mutation object which a given mutation probability. + * + * @param probability Mutation probability. The given probability is + * divided by the number of chromosomes of the genotype to form + * the concrete mutation probability. + * @param threshold Threshold value for strong and weak relations. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]}. + */ + protected CorrelationMutator(final double probability, final double threshold, final Correlations correlations) { + super(probability); + + this.correlations = correlations; + this.threshold = threshold; + } + + /** + * Concrete implementation of the alter method. It uses the following + * mutation methods: {@link #mutate(Phenotype, long, double, RandomGenerator)}, + * {@link #mutate(Genotype, double, RandomGenerator)}, + * {@link #mutate(Chromosome, double, CC, RandomGenerator)}, {@link #mutate(Gene, CC, RandomGenerator)}, + * in this specific order. + * + * @see #mutate(Phenotype, long, double, RandomGenerator) + * @see #mutate(Genotype, double, RandomGenerator) + * @see #mutate(Chromosome, double, CC, RandomGenerator) + * @see #mutate(Gene, CC, RandomGenerator) + */ + @Override + public AltererResult<G, C> alter( + final Seq<Phenotype<G, C>> population, + final long generation + ) { + assert population != null : "Not null is guaranteed from base class."; + + final RandomGenerator random = RandomRegistry.random(); + final double p = pow(_probability, 1.0/3.0); + final int P = Probabilities.toInt(p); + + final Seq<MutatorResult<Phenotype<G, C>>> result = population + .map(pt -> random.nextInt() < P + ? mutate(pt, generation, p, random) + : new MutatorResult(pt, 0)); + + return new AltererResult( + result.map(MutatorResult::result).asISeq(), + result.stream().mapToInt(MutatorResult::mutations).sum() + ); + } + + /** + * Mutates the given phenotype. + * + * @see #mutate(Genotype, double, RandomGenerator) + * @see #mutate(Chromosome, double, CC, RandomGenerator) + * @see #mutate(Gene, CC, RandomGenerator) + * + * @param phenotype the phenotype to mutate + * @param generation the actual generation + * @param p the mutation probability for the underlying genetic objects + * @param random the random engine used for the phenotype mutation + * @return the mutation result + */ + protected MutatorResult<Phenotype<G, C>> mutate( + final Phenotype<G, C> phenotype, + final long generation, + final double p, + final RandomGenerator random + ) { + final MutatorResult<Genotype<G>> result = mutate(phenotype.genotype(), p, random); + + return new MutatorResult(Phenotype.of(result.result(), generation), result.mutations()); + } + + /** + * Mutates the given genotype. + * + * @see #mutate(Chromosome, double, CC, RandomGenerator) + * @see #mutate(Gene, CC, RandomGenerator) + * + * @param genotype the genotype to mutate + * @param p the mutation probability for the underlying genetic objects + * @param random the random engine used for the genotype mutation + * @return the mutation result + */ + protected MutatorResult<Genotype<G>> mutate( + final Genotype<G> genotype, + final double p, + final RandomGenerator random + ) { + final int P = Probabilities.toInt(p); + final long count = genotype.stream().count(); + final CC [] contexts = createContextArray((int)count); + final List<MutatorResult<Chromosome<G>>> results = new ArrayList<>((int)count); + + for(int i = 0; i < count; ++i) { + final Chromosome<G> chromosome = genotype.get(i); + final boolean shouldMutate = random.nextInt() < P; + + if(!shouldMutate) { + results.add(new MutatorResult(chromosome, 0)); + } else { + final CC context = contexts[i]; + final MutatorResult<Chromosome<G>> mutatorResult = mutate(chromosome, p, context, random); + propagateCorrelationInfo(contexts, genotype, i); + + results.add(mutatorResult); + } + } + + final ISeq<MutatorResult<Chromosome<G>>> result = results.stream().collect(ISeq.toISeq()); + + return new MutatorResult( + Genotype.of(result.map(MutatorResult::result)), + result.stream().mapToInt(MutatorResult::mutations).sum() + ); + } + + protected void propagateCorrelationInfo(final CC [] contexts, final Genotype<G> genotype, final int i) { + final CC context = contexts[i]; + + for(final Correlation correlation : this.correlations.find(genotype, i)) { + final int targetIndex = Math.max(correlation.getChromosomeOne(), correlation.getChromosomeTwo()); + + contexts[targetIndex].apply(context, correlation); + propagateCorrelationInfo(contexts, genotype, targetIndex); + } + } + + /** + * Creates an array of uninitialized contexts. + * @param count Size of array. + * @return A newly created array. + */ + protected abstract CC[] createContextArray(final int count); + + /** + * Mutates the given chromosome. + * + * @see #mutate(Gene, CC, RandomGenerator) + * + * @param chromosome the chromosome to mutate + * @param p the mutation probability for the underlying genetic objects + * @param context the context of the mutation. Should be modified. + * @param random the random engine used for the genotype mutation + * @return the mutation result + */ + protected MutatorResult<Chromosome<G>> mutate( + final Chromosome<G> chromosome, + final double p, + final CC context, + final RandomGenerator random + ) { + final int P = Probabilities.toInt(p); + + final ISeq<MutatorResult<G>> result = chromosome.stream() + .map(gene -> random.nextInt() < P + ? new MutatorResult<G>(mutate(gene, context, random), 1) + : new MutatorResult<G>(gene, 0)) + .collect(ISeq.toISeq()); + + return new MutatorResult( + chromosome.newInstance(result.map(MutatorResult::result)), + result.stream().mapToInt(MutatorResult::mutations).sum() + ); + } + + /** + * Mutates the given gene. + * + * @param gene the gene to mutate + * @param context the context for mutation. The parameter should be modified + * @param random the random engine used for the genotype mutation‚ + * @return the mutation result + */ + protected G mutate(final G gene, final CC context, final RandomGenerator random) { + return gene.newInstance(); + } + + /** + * Creates an uninitialized context. + * + * @return an uninitialized context + */ + protected abstract CC uninitializedContext(); + + @Override + public String toString() { + return format("%s[p=%f]", getClass().getSimpleName(), _probability); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/CorrelationMutatorFactory.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/CorrelationMutatorFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..25b3c926ecfca732df4661812c144d695a91d454 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/CorrelationMutatorFactory.java @@ -0,0 +1,25 @@ +package de.evoal.core.main.ea.alterer.mutator; + +import de.evoal.core.api.board.Blackboard; +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.Alterer; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Inject; +import java.util.function.BiFunction; + +@ApplicationScoped +public final class CorrelationMutatorFactory { + private final static Logger log = LoggerFactory.getLogger(CorrelationMutatorFactory.class); + + @Inject + private Blackboard board; + + @Produces + public BiFunction<Double, Double, Alterer> create(final Correlations correlations) { + return (probability, threshold) -> new GaussianCorrelationMutator(probability, threshold, correlations); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/CorrelationMutatorMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/CorrelationMutatorMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..e7a7eee4a74fe817ed7dfdf126a2bbd4af3b4a1b --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/CorrelationMutatorMemento.java @@ -0,0 +1,12 @@ +package de.evoal.core.main.ea.alterer.mutator; + + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; + +/** + * Memento to preserve the state of correlation-aware mutator. + * @param <CC> + */ +public interface CorrelationMutatorMemento<CC extends CorrelationMutatorMemento> { + public void apply(final CC context, Correlation correlation); +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/GaussianCorrelationMutator.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/GaussianCorrelationMutator.java new file mode 100644 index 0000000000000000000000000000000000000000..66c1b3050dbdcba41979249d73d56763b7412670 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/GaussianCorrelationMutator.java @@ -0,0 +1,57 @@ +package de.evoal.core.main.ea.alterer.mutator; + +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.NumericGene; + +import java.util.random.RandomGenerator; + +import static io.jenetics.internal.math.Basics.clamp; +import static java.lang.Math.nextDown; +import static java.lang.String.format; + +public class GaussianCorrelationMutator< + G extends NumericGene<?, G>, + C extends Comparable<? super C>> + extends CorrelationMutator<G, C, GaussianCorrelationMutatorMemento> { + + public GaussianCorrelationMutator(final double probability, final double threshold, final Correlations correlations) { + super(probability, threshold, correlations); + } + + @Override + protected GaussianCorrelationMutatorMemento[] createContextArray(final int count) { + final GaussianCorrelationMutatorMemento[] result = new GaussianCorrelationMutatorMemento[count]; + + for(int i = 0; i < count; ++i) { + result[i] = uninitializedContext(); + } + + return result; + } + + @Override + protected G mutate(final G gene, final GaussianCorrelationMutatorMemento context, final RandomGenerator random) { + return mutate0(gene, context, random); + } + + private G mutate0(final G gene, final GaussianCorrelationMutatorMemento context, final RandomGenerator random) { + final double min = gene.min().doubleValue(); + final double max = gene.max().doubleValue(); + final double std = (max - min)*0.25; + + final double value = gene.doubleValue(); + final double gaussian = context.getGaussian(); + + return gene.newInstance(clamp(gaussian*std + value, min, nextDown(max))); + } + + @Override + public String toString() { + return format("%s[p=%f]", getClass().getSimpleName(), _probability); + } + + @Override + protected GaussianCorrelationMutatorMemento uninitializedContext() { + return new GaussianCorrelationMutatorMemento(threshold); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/GaussianCorrelationMutatorMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/GaussianCorrelationMutatorMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..15a4c49cff89e42fd8ed9cbd425c32d296eec560 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/GaussianCorrelationMutatorMemento.java @@ -0,0 +1,35 @@ +package de.evoal.core.main.ea.alterer.mutator; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import lombok.Data; + +import java.util.Random; + +@Data +public final class GaussianCorrelationMutatorMemento implements CorrelationMutatorMemento<GaussianCorrelationMutatorMemento> { + /** + * Random gaussian value for altering value. + */ + private double gaussian = new Random().nextGaussian(); + + private final double threshold; + + public GaussianCorrelationMutatorMemento(final double threshold) { + this.threshold = threshold; + } + + @Override + public void apply(final GaussianCorrelationMutatorMemento context, final Correlation correlation) { + final double correlationFactor = correlation.getCorrelationFactor(); + + if(correlationFactor >= threshold) { + gaussian = context.getGaussian(); + } else if(correlationFactor >= 0.0) { + gaussian = (context.getGaussian() + gaussian) / 2.0; + } else if(correlationFactor > -threshold) { + gaussian = (-context.getGaussian() + gaussian) / 2.0; + } else { + gaussian = -context.getGaussian(); + } + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SingleBitFlipCorrelationMutator.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SingleBitFlipCorrelationMutator.java new file mode 100644 index 0000000000000000000000000000000000000000..5eddb9e06a6a33e308b815e6df88f5d41b0a5d01 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SingleBitFlipCorrelationMutator.java @@ -0,0 +1,117 @@ +package de.evoal.core.main.ea.alterer.mutator; + +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.*; +import io.jenetics.util.MSeq; + +import java.util.Random; +import java.util.random.RandomGenerator; + +public class SingleBitFlipCorrelationMutator< + C extends Comparable<? super C> + > + extends CorrelationMutator<BitGene, C, SingleBitFlipCorrelationMutatorMemento> +{ + + /** + * Constructs an alterer with a given filip probability. + * + * @param probability the flip probability. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]}. + */ + public SingleBitFlipCorrelationMutator(final double probability, final double threshold, final Correlations correlations) { + super(probability, threshold, correlations); + } + + @Override + protected SingleBitFlipCorrelationMutatorMemento[] createContextArray(final int count) { + final SingleBitFlipCorrelationMutatorMemento[] result = new SingleBitFlipCorrelationMutatorMemento[count]; + + for(int i = 0; i < count; ++i) { + result[i] = uninitializedContext(); + } + + return result; + } + + /** + * Flips a random gene in the . + */ + @Override + protected MutatorResult<Chromosome<BitGene>> mutate(final Chromosome<BitGene> chromosome, + final double p, + final SingleBitFlipCorrelationMutatorMemento memento, + final RandomGenerator random) { + final MSeq<BitGene> genes = MSeq.of(chromosome); + + int index = memento.getIndex(random, genes.length(), p); + SingleBitFlipCorrelationMutatorMemento.FlipDirection direction = SingleBitFlipCorrelationMutatorMemento.FlipDirection.UNSET; + boolean newBit = false; + + switch(memento.getDirection()) { + case UNSET: { + newBit = !genes.get(index).bit(); + direction = SingleBitFlipCorrelationMutatorMemento.FlipDirection.from(newBit); + break; + } + + case SINGLE_FLIP_UP: { + newBit = true; + direction = SingleBitFlipCorrelationMutatorMemento.FlipDirection.UP; + break; + } + + case SINGLE_FLIP_DOWN: { + newBit = false; + direction = SingleBitFlipCorrelationMutatorMemento.FlipDirection.DOWN; + break; + } + + case TRANSITIVE_UP: { + index = findNearestIndex(genes, index, false); + newBit = true; + direction = SingleBitFlipCorrelationMutatorMemento.FlipDirection.UP; + } + + case TRANSITIVE_DOWN: { + index = findNearestIndex(genes, index, true); + newBit = false; + direction = SingleBitFlipCorrelationMutatorMemento.FlipDirection.DOWN; + } + } + + genes.set(index, BitGene.of(newBit)); + memento.setDirection(direction); + + return new MutatorResult( + chromosome.newInstance(genes.toISeq()), + 1 + ); + } + + private int findNearestIndex(final MSeq<BitGene> genes, final int starting, final boolean value) { + if(genes.get(starting).bit() == value) { + return starting; + } + + for(int i = 1; i < genes.length(); ++i) { + final int upperIndex = Math.min(starting + i, genes.length() - 1); + if(genes.get(upperIndex).bit() == value) { + return upperIndex; + } + + final int lowerIndex = Math.max(starting - i, 0); + if(genes.get(lowerIndex).bit() == value) { + return lowerIndex; + } + } + + return starting; + } + + @Override + protected SingleBitFlipCorrelationMutatorMemento uninitializedContext() { + return new SingleBitFlipCorrelationMutatorMemento(threshold); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SingleBitFlipCorrelationMutatorMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SingleBitFlipCorrelationMutatorMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..a1fa9d382f041b219e7646cf84b5f5ee60f35a1c --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SingleBitFlipCorrelationMutatorMemento.java @@ -0,0 +1,70 @@ +package de.evoal.core.main.ea.alterer.mutator; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import lombok.Getter; +import lombok.Setter; + +import java.util.Random; +import java.util.random.RandomGenerator; + +public class SingleBitFlipCorrelationMutatorMemento implements CorrelationMutatorMemento<SingleBitFlipCorrelationMutatorMemento> { + + public enum FlipDirection { + DOWN, + NOTHING, + SINGLE_FLIP_UP, + SINGLE_FLIP_DOWN, + TRANSITIVE_UP, + TRANSITIVE_DOWN, + UNSET, + UP; + + public static FlipDirection from(final boolean value) { + return value ? UP : DOWN; + } + } + + private int index = -1; + + private final double threshold; + + @Getter @Setter + private FlipDirection direction = FlipDirection.UNSET; + + public SingleBitFlipCorrelationMutatorMemento(final double threshold) { + this.threshold = threshold; + } + + public int getIndex(final RandomGenerator random, final int length, final double probability) { + if(index == -1) { + index = random.nextInt(0, length); + } + + return index; + } + + @Override + public void apply(final SingleBitFlipCorrelationMutatorMemento context, final Correlation correlation) { + final double correlationFactor = correlation.getCorrelationFactor(); + final FlipDirection direction = context.getDirection(); + + if(correlationFactor >= threshold && FlipDirection.UP.equals(direction)) { + this.direction = FlipDirection.TRANSITIVE_UP; + } else if(correlationFactor >= threshold && FlipDirection.DOWN.equals(direction)) { + this.direction = FlipDirection.TRANSITIVE_DOWN; + } else if(correlationFactor >= 0 && FlipDirection.UP.equals(direction)) { + this.direction = FlipDirection.SINGLE_FLIP_UP; + } else if(correlationFactor >= 0 && FlipDirection.DOWN.equals(direction)) { + this.direction = FlipDirection.SINGLE_FLIP_DOWN; + } else if(correlationFactor > -threshold && FlipDirection.UP.equals(direction)) { + this.direction = FlipDirection.SINGLE_FLIP_DOWN; + } else if(correlationFactor > -threshold && FlipDirection.DOWN.equals(direction)) { + this.direction = FlipDirection.SINGLE_FLIP_UP; + } else if(correlationFactor <= -threshold && FlipDirection.UP.equals(direction)) { + this.direction = FlipDirection.TRANSITIVE_DOWN; + } else if(correlationFactor <= -threshold && FlipDirection.DOWN.equals(direction)) { + this.direction = FlipDirection.TRANSITIVE_UP; + } + + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SingleBitFlipMutator.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SingleBitFlipMutator.java new file mode 100644 index 0000000000000000000000000000000000000000..aa03b14f1477fce7f0ecab66e6d3ab56a12498a5 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SingleBitFlipMutator.java @@ -0,0 +1,57 @@ +package de.evoal.core.main.ea.alterer.mutator; + +import io.jenetics.*; +import io.jenetics.util.MSeq; + +import java.util.Random; +import java.util.random.RandomGenerator; + + +public class SingleBitFlipMutator< + C extends Comparable<? super C> + > + extends Mutator<BitGene, C> +{ + + /** + * Constructs an alterer with a given filip probability. + * + * @param probability the flip probability. + * @throws IllegalArgumentException if the {@code probability} is not in the + * valid range of {@code [0, 1]}. + */ + public SingleBitFlipMutator(final double probability) { + super(probability); + } + + /** + * Default constructor, with default mutation probability + * ({@link AbstractAlterer#DEFAULT_ALTER_PROBABILITY}). + */ + public SingleBitFlipMutator() { + this(DEFAULT_ALTER_PROBABILITY); + } + + /** + * Flips a random gene in the . + */ + @Override + protected MutatorResult<Chromosome<BitGene>> mutate( + final Chromosome<BitGene> chromosome, + final double p, + final RandomGenerator random + ) { + final MSeq<BitGene> genes = MSeq.of(chromosome); + + final int index = random.nextInt(); + + final BitGene gene = genes.get(index); + genes.set(index, BitGene.of(!gene.bit())); + + return new MutatorResult( + chromosome.newInstance(genes.toISeq()), + 1 + ); + } + +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SwapCorrelationMutator.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SwapCorrelationMutator.java new file mode 100644 index 0000000000000000000000000000000000000000..7f3dfc111edf9bafbd210db37b59cd303f1e739e --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SwapCorrelationMutator.java @@ -0,0 +1,63 @@ +package de.evoal.core.main.ea.alterer.mutator; + +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import io.jenetics.Chromosome; +import io.jenetics.Gene; +import io.jenetics.MutatorResult; +import io.jenetics.util.MSeq; + +import java.util.Arrays; +import java.util.random.RandomGenerator; + +public class SwapCorrelationMutator< + G extends Gene<?, G>, + C extends Comparable<? super C> + > extends CorrelationMutator<G, C, SwapCorrelationMutatorMemento> { + + public SwapCorrelationMutator(final double probability, final double threshold, final Correlations correlations) { + super(probability, threshold, correlations); + } + + @Override + protected SwapCorrelationMutatorMemento[] createContextArray(final int count) { + final SwapCorrelationMutatorMemento[] result = new SwapCorrelationMutatorMemento[count]; + + for(int i = 0; i < count; ++i) { + result[i] = uninitializedContext(); + } + + return result; + } + + @Override + protected MutatorResult<Chromosome<G>> mutate( + final Chromosome<G> chromosome, + final double p, + final SwapCorrelationMutatorMemento memento, + final RandomGenerator random + ) { + final MutatorResult<Chromosome<G>> result; + if (chromosome.length() > 1) { + final MSeq<G> genes = MSeq.of(chromosome); + final int [][] swaps = memento.getIndices(random, genes.length(), p); + + final int mutations = (int) Arrays.stream(swaps) + .peek(i -> genes.swap(i[0], i[1])) + .count(); + result = new MutatorResult( + chromosome.newInstance(genes.toISeq()), + mutations + ); + } else { + result = new MutatorResult(chromosome, 0); + } + + return result; + + } + + @Override + protected SwapCorrelationMutatorMemento uninitializedContext() { + return new SwapCorrelationMutatorMemento(); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SwapCorrelationMutatorMemento.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SwapCorrelationMutatorMemento.java new file mode 100644 index 0000000000000000000000000000000000000000..b8e71b71fe6489ee150adb7705651fdb0ed93436 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/alterer/mutator/SwapCorrelationMutatorMemento.java @@ -0,0 +1,24 @@ +package de.evoal.core.main.ea.alterer.mutator; + +import de.evoal.core.main.ea.functions.correlation.model.Correlation; + +import java.util.Random; +import java.util.random.RandomGenerator; + +import static io.jenetics.internal.math.Randoms.indexes; + +public class SwapCorrelationMutatorMemento implements CorrelationMutatorMemento<SwapCorrelationMutatorMemento> { + private int [][] indices = null; + + public int [][] getIndices(final RandomGenerator random, final int length, final double probability) { + if(indices == null) { + indices = indexes(random, length, probability).mapToObj(i -> new int[] {i, random.nextInt(length)}).toArray(i -> new int [i][]); + } + + return indices; + } + + @Override + public void apply(final SwapCorrelationMutatorMemento context, final Correlation correlation) { + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/Correlation.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/Correlation.java new file mode 100644 index 0000000000000000000000000000000000000000..450f855557b82a12672fbd3b88429313ec2ea66a --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/Correlation.java @@ -0,0 +1,38 @@ +package de.evoal.core.main.ea.functions.correlation.model; + +import de.evoal.core.api.ea.codec.CustomCodec; +import lombok.Data; + +/** + * Base class for correlations. Represents a correlation between two chromosomes. + */ +@Data +public class Correlation { + /** + * Index of first chromosome. + */ + private int chromosomeOne; + + /** + * Index of second chromosome. + */ + private int chromosomeTwo; + + /** + * Codec for encoding and decoding between domain and ea values. + */ + private CustomCodec codec; + + /** + * Correlation factor + */ + private double correlationFactor; + + public boolean matchesSource(int chromosomeIndex, double chromosomeValue) { + return chromosomeOne == chromosomeIndex; + } + + public boolean matchesTarget(int chromosomeIndex, double chromosomeValue) { + return chromosomeTwo == chromosomeIndex; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/Correlations.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/Correlations.java new file mode 100644 index 0000000000000000000000000000000000000000..bd199bba1b1d14c24efe7d3de0bdfd16fa56f127 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/Correlations.java @@ -0,0 +1,62 @@ +package de.evoal.core.main.ea.functions.correlation.model; + +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.properties.Properties; +import io.jenetics.Gene; +import io.jenetics.Genotype; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Root class for all existing correlations. + */ +@Data +public class Correlations<G extends Gene<?, G>> { + /** + * Codec for encoding and decoding between domain and ea values. + */ + private CustomCodec<G> codec; + + /** + * The concrete list of all correlations. + */ + private final List<Correlation> correlations = new ArrayList<>(); + + public Correlations(final CustomCodec<G> codec) { + this.codec = codec; + } + + public List<Correlation> find(final Genotype<G> genotype, final int chromosomeIndex) { + final Properties properties = codec.decode(genotype); + + return correlations.stream() + .filter(c -> c.matchesSource(chromosomeIndex, properties.get(chromosomeIndex))) + .collect(Collectors.toList()); + } + + public int findCorrelationRoot(final Genotype<G> genotype, final int chromosomeIndex) { + final Properties properties = codec.decode(genotype); + + final double chromosomeValue = properties.get(chromosomeIndex); + + for(final Correlation correlation : correlations) { + if(!correlation.matchesTarget(chromosomeIndex, chromosomeValue)) { + continue; + } + + final int sourceIndex = correlation.getChromosomeOne(); + final double sourceValue = properties.get(sourceIndex); + + if(!correlation.matchesSource(sourceIndex, sourceValue)) { + continue; + } + + return findCorrelationRoot(genotype, sourceIndex); + } + + return chromosomeIndex; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/Range.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/Range.java new file mode 100644 index 0000000000000000000000000000000000000000..6b71bad7b9cecd2d3f3bce17e50592877e86b959 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/Range.java @@ -0,0 +1,17 @@ +package de.evoal.core.main.ea.functions.correlation.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@Data +@NoArgsConstructor +public class Range { + private double lower; + private double upper; + + public boolean includes(double chromosomeValue) { + return lower <= chromosomeValue && chromosomeValue <= upper; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/RangedCorrelation.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/RangedCorrelation.java new file mode 100644 index 0000000000000000000000000000000000000000..e4dc2468f61454425e55cbfc5254bfa9856117a7 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/ea/functions/correlation/model/RangedCorrelation.java @@ -0,0 +1,34 @@ +package de.evoal.core.main.ea.functions.correlation.model; + +import lombok.Data; + +@Data +public class RangedCorrelation extends Correlation { + /** + * Boundaries of first chromosome value. + */ + private Range chromosomeOneRange; + + /** + * Boundaries of second chromosome value. + */ + private Range chromosomeTwoRange; + + @Override + public boolean matchesSource(int chromosomeIndex, double chromosomeValue) { + if(!super.matchesSource(chromosomeIndex, chromosomeValue)) { + return false; + } + + return getChromosomeOneRange().includes(chromosomeValue); + } + + @Override + public boolean matchesTarget(int chromosomeIndex, double chromosomeValue) { + if(!super.matchesTarget(chromosomeIndex, chromosomeValue)) { + return false; + } + + return getChromosomeTwoRange().includes(chromosomeValue); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearch.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearch.java new file mode 100644 index 0000000000000000000000000000000000000000..63e6718c42b8969ca83a8cb4f38d83476d285ed0 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearch.java @@ -0,0 +1,169 @@ +package de.evoal.core.main.search; + +import java.io.File; +import java.time.Duration; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Function; + +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.board.Blackboard; +import de.evoal.core.api.cdi.BlackboardValue; +import de.evoal.core.api.cdi.ConfigurationValue; +import de.evoal.core.main.ea.alterer.AltererFactory; +import de.evoal.core.api.utils.LanguageHelper; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.ea.fitness.type.FitnessType; + +import de.evoal.core.api.ea.fitness.FitnessEvaluator; +import de.evoal.core.api.statistics.StatisticsWriter; +import de.evoal.languages.model.eal.EAModel; +import de.evoal.languages.model.instance.Array; +import de.evoal.languages.model.instance.Attribute; +import de.evoal.languages.model.instance.Name; +import de.evoal.languages.model.instance.Value; +import io.jenetics.*; +import io.jenetics.engine.*; +import io.jenetics.stat.MinMax; +import io.jenetics.util.Factory; +import lombok.extern.slf4j.Slf4j; + +import javax.enterprise.inject.Instance; + +import javax.inject.Inject; +import javax.inject.Named; + +@Slf4j +public class HeuristicSearch<G extends Gene<?, G>> { + @Inject + private Blackboard board; + + /** + * Location for storing the output. + */ + @Inject + @BlackboardValue(BlackboardEntry.EVALUATION_OUTPUT_FOLDER) + private File outputDirectory; + + /** + * The actual run + */ + @Inject + @BlackboardValue(BlackboardEntry.EVALUATION_RUN) + private String run; + + @Inject + @ConfigurationValue(entry = BlackboardEntry.EA_CONFIGURATION, access = "algorithm.number_of_generations") + private int numberOfGenerations = 100; + + @Inject + @ConfigurationValue(entry = BlackboardEntry.EA_CONFIGURATION, access = "algorithm.size_of_population") + private int sizeOfPopulation = 100; + + @Inject + @ConfigurationValue(entry = BlackboardEntry.EA_CONFIGURATION, access = "algorithm.maximum_age") + private int maximumAge = 100; + + private final Map<String, List<Alterer<G, FitnessType>>> alterers = new HashMap<>(); + + @Inject + private CustomCodec encoding; + + private final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + + @Inject + @Named("evaluator") + private FitnessEvaluator fitnessEvaluator; + + @Inject @Named("offspring") + private Selector offspringSelector; + + @Inject @Named("statistics") + private StatisticsWriter statistics; + + @Inject @Named("survivor") + private Selector survivorSelector; + + @Inject + private AltererFactory factory; + + @Inject + private Instance<List<Constraint>> constraints; + + + @Inject + private Function<Engine, EvolutionStream> initalStreamFactory; + + public void run() { + setup(); + + final Factory<Genotype<G>> gtf = encoding.encoding(); + + final Constraint<G, FitnessType> constraint = new ListConstraint(constraints.get()); + + final Engine<G, FitnessType> engine= Engine.builder(this.fitnessEvaluator, encoding) + .alterers(flattenAltererMap()) + .offspringSelector(this.offspringSelector) + .survivorsSelector(this.survivorSelector) + .optimize(Optimize.MAXIMUM) + .populationSize(sizeOfPopulation) + .constraint(constraint) + .maximalPhenotypeAge(maximumAge) + .executor(executor) + .build(); + + EvolutionStatistics<FitnessType, MinMax<FitnessType>> statistics = EvolutionStatistics.ofComparable(); + EvolutionStream<G, FitnessType> initialStream = initalStreamFactory.apply(engine); + + final EvolutionResult<G, FitnessType> result + = initialStream.limit(Limits.byFixedGeneration(numberOfGenerations)) + .limit(Limits.byExecutionTime(Duration.ofMinutes(5))) +// .parallel() + .peek(this.statistics::add) + .peek(statistics) + .collect(EvolutionResult.toBestEvolutionResult()); + this.statistics.write(); + + System.out.println(statistics); + + executor.shutdownNow(); + } + + private void setup() { + final EAModel configuration = board.get(BlackboardEntry.EA_CONFIGURATION); + + final de.evoal.languages.model.instance.Instance alterers = LanguageHelper.lookup(configuration.getInstance(), "algorithm.alterers"); + + for(final Attribute category: alterers.getAttributes()) { + final String name = ((Name)category.getName()).getName().getName(); + log.info("Processing alterer category '{}'.", name); + + final Array array = (Array) category.getValue(); + + for(final Value alterer : array.getValues()) { + this.alterers + .computeIfAbsent(name, k -> new ArrayList<>()) + .add(factory.create((de.evoal.languages.model.instance.Instance)alterer)); + } + } + + + } + + private Alterer<G, FitnessType> flattenAltererMap() { + Alterer<G, FitnessType> result = null; + + for(final Map.Entry<String, List<Alterer<G, FitnessType>>> entry : alterers.entrySet()) { + for(final Alterer<G, FitnessType> e : entry.getValue()) { + if(result == null) { + result = e; + } else { + result = Alterer.of(result, e); + } + } + } + + return result; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearchEvaluation.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearchEvaluation.java new file mode 100644 index 0000000000000000000000000000000000000000..ff621367b0d6c3dd245bc7db121c089d9eedee8a --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearchEvaluation.java @@ -0,0 +1,129 @@ +package de.evoal.core.main.search; + +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.BlackboardValue; +import de.evoal.core.api.cdi.MainClass; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesPair; +import de.evoal.core.api.board.Blackboard; +import javax.enterprise.context.ApplicationScoped; + +import de.evoal.core.api.statistics.Column; +import de.evoal.core.api.statistics.ColumnType; +import de.evoal.core.api.statistics.WriterContext; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.math3.util.Pair; +import org.apache.deltaspike.core.api.provider.BeanProvider; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.File; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Slf4j +@Named("evaluation") +@ApplicationScoped +public class HeuristicSearchEvaluation implements MainClass { + @Inject + private Blackboard board; + + @Inject + private WriterContext context; + + @Inject + @BlackboardValue(BlackboardEntry.EA_CONFIGURATION_FILE) + private String heuristicFile; + + @Inject + @BlackboardValue(BlackboardEntry.EVALUATION_ITERATIONS) + private int iterations; + + private File outputBaseDir; + + @Inject + @BlackboardValue(BlackboardEntry.PREDICTIVE_FUNCTION_FILE) + private String predictiveFile; + + @Inject + @BlackboardValue(BlackboardEntry.TRAINING_POINT_FILE) + private String pointsFile; + + private List<Pair<Properties, Properties>> targets; + + @Inject + @BlackboardValue(BlackboardEntry.TARGETS_FILE) + private String targetFile; + + @Inject + @Named("target-stream") + private Stream<PropertiesPair> targetStream; + + private Column targetColumn; + private Column runColumn; + + @Override + public void run() { + log.info("Running heuristic search evaluation with the following configuartion:"); + log.info(" predictive function loaded from ({}/{})", predictiveFile, pointsFile); + log.info(" target points loaded from ({})", targetFile); + log.info(" heuristic configuration loaded from ({})", heuristicFile); + log.info(" running {} iterations.", iterations); + + /* prepare output directory */ + this.outputBaseDir = new File(board.<String>get(BlackboardEntry.EVALUATION_OUTPUT_FOLDER)); + + targets = targetStream.collect(Collectors.toList()); + + log.info("Processing {} targets during evaluation.", targets.size()); + + final long startTime = System.currentTimeMillis(); + process(); + final long endTime = System.currentTimeMillis(); + + System.err.println("Calculation of heuristic search took in average " + (endTime - startTime)/iterations + " ms."); + } + + private void process() { + targetColumn = HeuristicSearchUtils.addColumn(context,"target", ColumnType.Integer, 0); + runColumn = HeuristicSearchUtils.addColumn(context,"run", ColumnType.Integer, 0); + + targets.forEach(this::processTarget); + } + + private int targetIndex = 0; + private void processTarget(final Pair<Properties, Properties> target) { + final int targetIndex = this.targetIndex++; + final int targetLength = calculateFigures(targets.size()); + final int runLength = calculateFigures(iterations); + + context.bindColumn(targetColumn, targetIndex); + + board.bind(BlackboardEntry.TARGET_PROPERTIES_SOURCE, target.getFirst()); + board.bind(BlackboardEntry.TARGET_PROPERTIES, target.getSecond()); + board.bind(BlackboardEntry.EVALUATION_OUTPUT_FOLDER, outputBaseDir); + + log.info("Evaluating with target {} -> {}.", targetIndex, target); + + for (int i = 0; i < iterations; ++i) { + log.info("Running {}/{}", i, iterations); + final String run = convertToString(i, runLength); + board.bind(BlackboardEntry.EVALUATION_RUN, run); + context.bindColumn(runColumn, i); + + final HeuristicSearch<?> search = new HeuristicSearch<>(); + BeanProvider.injectFields(search); + search.run(); + } + } + + private String convertToString(final int targetIndex, final int length) { + return String.format("%0" + Math.max(length, 1) + "d", targetIndex); + } + + private int calculateFigures(final int number) { + return (int)(Math.log10(number)); + } +} + diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearchMain.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearchMain.java new file mode 100644 index 0000000000000000000000000000000000000000..e1f1da1c3aecf39e621f9a12d3ce216872b974b8 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearchMain.java @@ -0,0 +1,57 @@ +package de.evoal.core.main.search; + +import de.evoal.core.api.board.Blackboard; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.BeanFactory; +import de.evoal.core.api.cdi.MainClass; +import de.evoal.core.api.properties.Properties; +import javax.enterprise.context.ApplicationScoped; + +import de.evoal.core.api.statistics.ColumnType; +import de.evoal.core.api.statistics.WriterContext; +import org.apache.commons.math3.util.Pair; +import org.apache.deltaspike.core.api.provider.BeanProvider; + +import javax.inject.Inject; +import javax.inject.Named; +import java.io.File; +import java.util.stream.Stream; + +@Named("heuristic-search") +@ApplicationScoped +public class HeuristicSearchMain implements MainClass { + @Inject + private Blackboard board; + + @Inject + private WriterContext context; + + @Override + public void run() { + final String predictiveFileName = board.get(BlackboardEntry.PREDICTIVE_FUNCTION_FILE); + final String heuristicFileName = board.get(BlackboardEntry.EA_CONFIGURATION_FILE); + + final File outputBaseDir = HeuristicSearchUtils.calculateOutputBaseDir(new File(predictiveFileName), new File(heuristicFileName)); + + HeuristicSearchUtils.addColumn(context,"target", ColumnType.Integer, 0); + HeuristicSearchUtils.addColumn(context,"run", ColumnType.Integer, 0); + + board.bind(BlackboardEntry.EVALUATION_OUTPUT_FOLDER, outputBaseDir); + board.bind(BlackboardEntry.EVALUATION_RUN, "0"); + + final Stream<Pair<Properties, Properties>> targets = (Stream<Pair<Properties, Properties>>)BeanProvider.getContextualReference("target-stream"); + setTarget(targets, board); + + BeanFactory.create(HeuristicSearch.class) + .run(); + } + + private static void setTarget(final Stream<Pair<Properties, Properties>> targets, final Blackboard board) { + final Pair<Properties, Properties> targetProperties = + targets.findFirst() + .orElseThrow(() -> {throw new IllegalStateException("No target point found");}); + + board.bind(BlackboardEntry.TARGET_PROPERTIES_SOURCE, targetProperties.getFirst()); + board.bind(BlackboardEntry.TARGET_PROPERTIES, targetProperties.getSecond()); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearchUtils.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearchUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..1c996ea81cce78dec212a8a349d4d6abc40c44b1 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/HeuristicSearchUtils.java @@ -0,0 +1,36 @@ +package de.evoal.core.main.search; + +import de.evoal.core.api.statistics.Column; +import de.evoal.core.api.statistics.ColumnType; +import de.evoal.core.api.statistics.WriterContext; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; + +/** + * Helper functions for loading stuff for heuristic search. + */ +@Slf4j +public final class HeuristicSearchUtils { + private HeuristicSearchUtils(){ + } + + public static Column addColumn(final WriterContext context, final String name, final ColumnType type, final Object value) { + final Column col = new Column(name, type); + context.addColumn(col); + context.bindColumn(col, value); + + return col; + } + + public static File calculateOutputBaseDir(final File predictiveFile, final File heuristicFile) { + String outputDirname = predictiveFile.toString(); + outputDirname = outputDirname.split("\\.")[0]; + outputDirname = outputDirname.replace("input/", "output/"); + final File outputDir = new File(outputDirname); + + return new File(outputDir, heuristicFile.getName().split("\\.")[0]); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/ListConstraint.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/ListConstraint.java new file mode 100644 index 0000000000000000000000000000000000000000..dd0cded2efd4a62aa2963eae6a9d4516f8386110 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/search/ListConstraint.java @@ -0,0 +1,32 @@ +package de.evoal.core.main.search; + +import de.evoal.core.api.ea.fitness.type.FitnessType; +import io.jenetics.Gene; +import io.jenetics.Phenotype; +import io.jenetics.engine.Constraint; + +import java.util.List; + +public class ListConstraint<G extends Gene<?, G>> implements Constraint<G, FitnessType> { + private final List<Constraint<G, FitnessType>> constraints; + + public ListConstraint(final List<Constraint<G, FitnessType>> constraints) { + this.constraints = constraints; + } + + @Override + public boolean test(final Phenotype<G, FitnessType> individual) { + return constraints.stream().allMatch(c -> c.test(individual)); + } + + @Override + public Phenotype<G, FitnessType> repair(Phenotype<G, FitnessType> individual, long generation) { + Phenotype<G, FitnessType> phenotype = individual; + + for(final Constraint<G, FitnessType> c : constraints) { + phenotype = c.repair(phenotype, generation); + } + + return phenotype; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/StatisticsFactory.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/StatisticsFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..864d339020fd4ac3b5c65042162ebca1def89a89 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/StatisticsFactory.java @@ -0,0 +1,35 @@ +package de.evoal.core.main.statistics; + +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.ConfigurationValue; +import de.evoal.core.api.statistics.StatisticsWriter; +import de.evoal.core.api.utils.Requirements; +import de.evoal.languages.model.instance.Array; +import de.evoal.languages.model.instance.Instance; +import de.evoal.core.main.statistics.internal.MultipleStatisticsWriter; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; + +import org.apache.commons.math3.util.Pair; +import org.apache.deltaspike.core.api.provider.BeanProvider; + +import javax.inject.Named; + +@ApplicationScoped +public class StatisticsFactory { + @Produces + @Named("statistics") + public StatisticsWriter create(final @ConfigurationValue(entry = BlackboardEntry.EA_CONFIGURATION, access = "statistics") Instance instance) { + Requirements.requireSize(instance.getAttributes(), 1); + final Array array = (Array)instance.getAttributes().get(0).getValue(); + + final StatisticsWriter [] writers = array.getValues() + .stream() + .map(Instance.class::cast) + .map(i -> new Pair<>(BeanProvider.getContextualReference(i.getName().getName(), false, StatisticsWriter.class), i)) + .map(p -> p.getFirst().init(p.getSecond())) + .toArray(i -> new StatisticsWriter[i]); + + return new MultipleStatisticsWriter(writers); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/constraint/ConstraintStatistics.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/constraint/ConstraintStatistics.java new file mode 100644 index 0000000000000000000000000000000000000000..dbbd336ad6f74ff2699c13609a0aae0a96cad1cc --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/constraint/ConstraintStatistics.java @@ -0,0 +1,143 @@ +package de.evoal.core.main.statistics.constraint; + +import de.evoal.core.api.statistics.*; +import de.evoal.core.main.ddl.constraint.strategies.CalculationFactory; +import de.evoal.core.main.ddl.constraint.strategies.CalculationStrategy; +import de.evoal.languages.model.instance.Instance; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.ea.constraints.model.ConstraintResult; +import de.evoal.core.api.ea.constraints.model.Constraints; +import de.evoal.core.api.ea.constraints.strategies.CalculationResult; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import de.evoal.core.api.properties.Properties; +import io.jenetics.Phenotype; +import io.jenetics.engine.EvolutionResult; +import io.jenetics.util.ISeq; +import javax.annotation.PostConstruct; +import javax.enterprise.context.Dependent; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.ArrayList; +import java.util.DoubleSummaryStatistics; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Small helper class for collecting and writing the generation-based statistics. + */ +@Slf4j +@Named("constraint-statistics") +@Dependent +public class ConstraintStatistics implements StatisticsWriter { + private final static int NUMBER_OF_STATISTICS_PER_CONSTRAINT = 5; + + private final List<Column> columns = new ArrayList<>(); + + @Inject + private CalculationFactory factory; + + @Inject + private Constraints constraints; + + @Inject + private CustomCodec codec; + + private CalculationStrategy[] calculators; + + private long endTime; + + private long startTime; + + @Inject + private WriterStrategy strategy; + + private Writer writer; + + @PostConstruct @SneakyThrows(WriterException.class) + private void init() { + startTime = System.currentTimeMillis(); + + createColumns(); + + writer = strategy.create("constraint-statistics", columns); + + calculators = constraints.getConstraints() + .stream() + .map(factory::create) + .toArray(CalculationStrategy[]::new); + } + + private void createColumns() { + columns.add(new Column("generation", ColumnType.Integer)); + + for(int i = 0; i < constraints.getConstraints().size(); ++i) { + columns.add(new Column("violatingIndividuals_" + i, ColumnType.Integer)); + columns.add(new Column("sumOfDifferences_" + i, ColumnType.Double)); + columns.add(new Column("minOfDifferences_" + i, ColumnType.Double)); + columns.add(new Column("avgOfDifferences_" + i, ColumnType.Double)); + columns.add(new Column("maxOfDifferences_" + i, ColumnType.Double)); + } + } + + private Object[] toData(final long generation, final ISeq<Phenotype<?, FitnessType>> population) { + final Object [] data = new Object[1 + constraints.getConstraints().size() * NUMBER_OF_STATISTICS_PER_CONSTRAINT]; + + data[0] = generation; + + for(int index = 0; index < calculators.length; ++index) { + final CalculationStrategy strategy = calculators[index]; + + final List<CalculationResult> calculationResults = + population.stream() + .map(Phenotype::genotype) + .map(g -> (Properties)codec.decode(g)) + .map(strategy::calculate) + .collect(Collectors.toList()); + + final long invalid = calculationResults + .stream() + .filter(r -> !r.isSuccessful()) + .count(); + + final DoubleSummaryStatistics statistics = + calculationResults + .stream() + .filter(r -> !r.isSuccessful()) + .map(CalculationResult::getResult) + .mapToDouble(ConstraintResult::getComparisonDifference) + .summaryStatistics(); + + data[1 + index * NUMBER_OF_STATISTICS_PER_CONSTRAINT + 0] = invalid; + data[1 + index * NUMBER_OF_STATISTICS_PER_CONSTRAINT + 1] = statistics.getSum(); + data[1 + index * NUMBER_OF_STATISTICS_PER_CONSTRAINT + 2] = statistics.getMin(); + data[1 + index * NUMBER_OF_STATISTICS_PER_CONSTRAINT + 3] = statistics.getAverage(); + data[1 + index * NUMBER_OF_STATISTICS_PER_CONSTRAINT + 4] = statistics.getMax(); + } + + return data; + } + + @SneakyThrows(WriterException.class) + public void add(final EvolutionResult<?, FitnessType> evolutionResult) { + final ISeq<Phenotype<?, FitnessType>> population = (ISeq<Phenotype<?, FitnessType>>)(Object)evolutionResult.population(); + + writer.addRecord(toData(evolutionResult.generation(), population)); + } + + @Override + public StatisticsWriter init(Instance configuration) { + return this; + } + + public void write() { + endTime = System.currentTimeMillis(); + try { + strategy.close(writer); + } catch (final WriterException e) { + log.error("Failed to write statistics:", e); + } + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/fitness/FitnessStatistics.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/fitness/FitnessStatistics.java new file mode 100644 index 0000000000000000000000000000000000000000..1dd91a28fa915a90531de9672e1d644f32da917d --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/fitness/FitnessStatistics.java @@ -0,0 +1,90 @@ +package de.evoal.core.main.statistics.fitness; + +import de.evoal.core.api.statistics.*; +import de.evoal.languages.model.instance.Instance; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import de.evoal.core.api.properties.PropertiesSpecification; +import io.jenetics.Phenotype; +import io.jenetics.engine.EvolutionResult; +import io.jenetics.util.ISeq; +import javax.annotation.PostConstruct; +import javax.enterprise.context.Dependent; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.LinkedList; +import java.util.List; + +/** + * Small helper class for collecting and writing the generation-based statistics. + */ +@Slf4j +@Named("fitness-per-individual") +@Dependent +public class FitnessStatistics implements StatisticsWriter { + @Inject @Named("target-properties-specification") + private PropertiesSpecification targetSpecification; + + @Inject + private WriterStrategy strategy; + + private Writer writer; + + @PostConstruct + @SneakyThrows(WriterException.class) + private void init() { + createWriter(); + } + + @Override + public StatisticsWriter init(Instance configuration) { + return this; + } + + private void createWriter() throws WriterException { + final List<Column> columns = new LinkedList<>(); + + columns.add(new Column("generation", ColumnType.Integer)); + columns.add(new Column("index", ColumnType.Integer)); + + for(int i = 0; i < targetSpecification.size(); ++i) { + columns.add(new Column("fitness-value-" + targetSpecification.getProperties().get(i).name(), ColumnType.Double)); + } + + writer = strategy.create("fitness-by-individual", columns); + } + + private Object[] dataOfPhenotype(final int index, final long generation, Phenotype<?, FitnessType> phenotype) { + final Object [] data = new Object[2 + targetSpecification.size()]; + + data[0] = generation; + data[1] = index; + + final double [] fitnessValues = phenotype.fitness().getFitnessValues(); + + for(int i = 0; i < fitnessValues.length; ++i) { + data[2 + i] = fitnessValues[i]; + } + + return data; + } + + @SneakyThrows(WriterException.class) + public void add(final EvolutionResult<?, FitnessType> evolutionResult) { + final ISeq<Phenotype<?, FitnessType>> population = (ISeq<Phenotype<?, FitnessType>>)(Object)evolutionResult.population(); + + for(int i = 0; i < population.size(); ++i) { + writer.addRecord(dataOfPhenotype(i, evolutionResult.generation(), population.get(i))); + } + } + + public void write() { + try { + strategy.close(writer); + } catch (final WriterException e) { + log.error("Failed to write statistics:", e); + } + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/individuals/IndividualStatistics.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/individuals/IndividualStatistics.java new file mode 100644 index 0000000000000000000000000000000000000000..6179819c72cb238a35f29c2ac1b7f1e48be2f156 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/individuals/IndividualStatistics.java @@ -0,0 +1,104 @@ +package de.evoal.core.main.statistics.individuals; + +import de.evoal.core.api.statistics.*; +import de.evoal.languages.model.instance.Instance; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import io.jenetics.Genotype; +import io.jenetics.Phenotype; +import io.jenetics.engine.EvolutionResult; +import io.jenetics.util.ISeq; +import javax.annotation.PostConstruct; +import javax.enterprise.context.Dependent; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +import javax.inject.Inject; +import javax.inject.Named; +import java.util.ArrayList; +import java.util.List; + +/** + * Small helper class for collecting and writing the generation-based statistics. + */ +@Slf4j +@Named("individuals") +@Dependent +public class IndividualStatistics implements StatisticsWriter { + /** + * Encoding for converting between ea and domain. + */ + @Inject + private CustomCodec encoding; + + @Inject @Named("source-properties-specification") + private PropertiesSpecification sourceSpecification; + + @Inject + private WriterStrategy strategy; + + private Writer writer; + + @PostConstruct + @SneakyThrows(WriterException.class) + private void init() { + createWriter(); + } + + @Override + public StatisticsWriter init(Instance configuration) { + return this; + } + + private void createWriter() throws WriterException { + final List<Column> columns = new ArrayList<>(); + + columns.add(new Column("generation", ColumnType.Integer)); + columns.add(new Column("index", ColumnType.Integer)); + columns.add(new Column("individual", ColumnType.String)); + columns.add(new Column("age", ColumnType.Integer)); + + for(int i = 0; i < sourceSpecification.size(); ++i) { + columns.add(new Column(sourceSpecification.getProperties().get(i).name(), ColumnType.Double)); + } + + writer = strategy.create("individuals", columns); + } + + private Object[] dataOfPhenotype(final int index, final long generation, Phenotype<?, FitnessType> phenotype) { + final Object [] data = new Object[4 + sourceSpecification.size()]; + + final Genotype<?> genotype = phenotype.genotype(); + final Properties individual = (Properties) encoding.decode(genotype); + + data[0] = generation; + data[1] = index; + data[2] = individual.toString(); + data[3] = phenotype.age(generation); + + for(int i = 0; i < individual.size(); ++i) { + data[4 + i] = individual.getValues()[i]; + } + + return data; + } + + @SneakyThrows(WriterException.class) + public void add(final EvolutionResult<?, FitnessType> evolutionResult) { + final ISeq<Phenotype<?, FitnessType>> population = (ISeq<Phenotype<?, FitnessType>>)(Object)evolutionResult.population(); + + for(int i = 0; i < population.size(); ++i) { + writer.addRecord(dataOfPhenotype(i, evolutionResult.generation(), population.get(i))); + } + } + + public void write() { + try { + strategy.close(writer); + } catch (final WriterException e) { + log.error("Failed to write statistics:", e); + } + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/internal/MultipleStatisticsWriter.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/internal/MultipleStatisticsWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..0b08abaaccf281ae08ef6d846f0280110928257e --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/internal/MultipleStatisticsWriter.java @@ -0,0 +1,31 @@ +package de.evoal.core.main.statistics.internal; + +import de.evoal.languages.model.instance.Instance; +import de.evoal.core.api.statistics.StatisticsWriter; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import io.jenetics.engine.EvolutionResult; + +import java.util.Arrays; + +public class MultipleStatisticsWriter implements StatisticsWriter { + private final StatisticsWriter[] writers; + + public MultipleStatisticsWriter(final StatisticsWriter ... writers) { + this.writers = writers; + } + + @Override + public void add(final EvolutionResult<?, FitnessType> evolutionResult) { + Arrays.stream(writers).forEach(w -> w.add(evolutionResult)); + } + + @Override + public StatisticsWriter init(Instance configuration) { + return this; + } + + @Override + public void write() { + Arrays.stream(writers).forEach(StatisticsWriter::write); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/nop/NopStatistics.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/nop/NopStatistics.java new file mode 100644 index 0000000000000000000000000000000000000000..e0756ab82805db61dc82e657a27f3b80847ba2b7 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/nop/NopStatistics.java @@ -0,0 +1,26 @@ +package de.evoal.core.main.statistics.nop; + +import de.evoal.languages.model.instance.Instance; +import de.evoal.core.api.statistics.StatisticsWriter; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import io.jenetics.engine.EvolutionResult; +import javax.enterprise.context.Dependent; + +import javax.inject.Named; + +@Named("none") +@Dependent +public class NopStatistics implements StatisticsWriter { + @Override + public void add(final EvolutionResult<?, FitnessType> evolutionResult) { + } + + @Override + public StatisticsWriter init(Instance configuration) { + return this; + } + + @Override + public void write() { + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/GenerationStatisticsWriter.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/GenerationStatisticsWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..1ae8f06375ffb7fe274a5062e79b05aada938c2b --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/GenerationStatisticsWriter.java @@ -0,0 +1,193 @@ +package de.evoal.core.main.statistics.rangeCorrelated; + +import de.evoal.core.api.statistics.*; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.main.ea.functions.correlation.model.Correlation; +import de.evoal.core.main.ea.functions.correlation.model.Correlations; +import de.evoal.core.main.ea.functions.correlation.model.RangedCorrelation; +import de.evoal.languages.model.instance.Instance; +import io.jenetics.Genotype; +import io.jenetics.engine.EvolutionResult; +import java.util.*; +import javax.annotation.PostConstruct; +import javax.enterprise.context.Dependent; +import javax.inject.Inject; +import javax.inject.Named; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.math3.util.Pair; + + +/** + * Small helper class for collecting and writing the generation-based statistics. + */ +@Slf4j +@Named("range-correlated") +@Dependent +public class GenerationStatisticsWriter implements StatisticsWriter { + /** + * Encoding for converting between ea and domain. + */ + @Inject + private CustomCodec encoding; + + @Inject @Named("genotype-limits") + private List<Pair<Double, Double>> limits; + + @Inject + private Correlations correlations; + + private List<Hypercube> hypercubeDefinitions; + + private List<Hypercube> initialGenerationCubes; + + @Inject + private WriterStrategy strategy; + + private Writer writer; + + /** + * Creates a new GenerationStatistics instance. + */ + @PostConstruct + @SneakyThrows(WriterException.class) + public void init() { + final List<PropertyRange> rangesOfProperties = extractPropertyRangesFromCorrelations(); + + hypercubeDefinitions = generateHypercubesFromRanges(rangesOfProperties, limits.size()); + + createWriter(); + } + + private void createWriter() throws WriterException { + final List<Column> columns = new LinkedList<>(); + + columns.add(new Column("generation", ColumnType.Integer)); + + for(int i = 0; i < hypercubeDefinitions.size(); ++i) { + columns.add(new Column("hypercube-" + (i+1), ColumnType.Double)); + } + + writer = strategy.create("hypercube-correlation-distances", columns); + } + + private List<PropertyRange> extractPropertyRangesFromCorrelations() { + final List<PropertyRange> result = new ArrayList<>(); + + final List<Correlation> allTheCorrelations = correlations.getCorrelations(); + + allTheCorrelations + .stream() + .filter(RangedCorrelation.class::isInstance) + .map(RangedCorrelation.class::cast) + .forEach(rc -> { + result.add(new PropertyRange(rc.getChromosomeOne(), rc.getChromosomeOneRange())); + result.add(new PropertyRange(rc.getChromosomeTwo(), rc.getChromosomeTwoRange())); + }); + + return result; + } + + @Override + public void add(final EvolutionResult<?, FitnessType> evolutionResult) { + if(evolutionResult.generation() == 1) { + initialGenerationCubes = fillHypercubes(evolutionResult); + } + + final List<Hypercube> currentCubes = fillHypercubes(evolutionResult); + double[] arrayOfDistances = new double[initialGenerationCubes.size()]; + for(int i=0; i< arrayOfDistances.length; i++) { + arrayOfDistances[i] = currentCubes.get(i).computeSquaredDistanceToCovarianceMatrix(initialGenerationCubes.get(i)); + } + + Object[] data = new Object[1+ arrayOfDistances.length + 1]; + data[0] = evolutionResult.generation(); + double sum = 0.0; + for(int i = 0; i < arrayOfDistances.length; i++) { + data[i+1] = arrayOfDistances[i]; + sum += arrayOfDistances[i]; + } + data[arrayOfDistances.length + 1] = sum; + try { + writer.addRecord(data); + } catch (Exception e) { + log.error("The csv printing didn't work in generation {}", evolutionResult.generation(), e); + } + } + + private List<Hypercube> fillHypercubes(final EvolutionResult<?, FitnessType> evolutionResult){ + List<Hypercube> currentGeneration = new ArrayList<>(); + for(int j = 0; j < hypercubeDefinitions.size(); j++) { + Hypercube hypercube = new Hypercube(hypercubeDefinitions.get(j)); + for(int i= 0; i < evolutionResult.population().asList().size(); i++) { + final Genotype<?> genotype = evolutionResult.population().asList().get(i).genotype(); + final Properties domainValues = (Properties) encoding.decode(genotype); + + hypercube.addDataPoint(domainValues); + } + currentGeneration.add(hypercube); + } + return currentGeneration; + } + + public void write() { + try { + strategy.close(writer); + } catch (final WriterException e) { + log.error("Failed to write statistics:", e); + } + } + + /** + * This methods builds all hypercubes resulting from the given ranges of the dvl. It need only be performed once., + * @param listOfRanges + * @return + */ + public List<Hypercube> generateHypercubesFromRanges(final List<PropertyRange> listOfRanges, final int dimensions){ + + List<SortedSet<Double>> listOfBoundaries = new ArrayList<>(dimensions); + for(int i=0; i<dimensions; i++) { + final SortedSet<Double> boundaries = new TreeSet<>(); + boundaries.add(limits.get(i).getFirst()); + boundaries.add(limits.get(i).getSecond()); + + listOfBoundaries.add(boundaries); + } + + //sorts every range into the correct dimension and sorts the interval values afterwards. + for(int j = 0; j < listOfRanges.size(); j++) { + PropertyRange currentRange = listOfRanges.get(j); + listOfBoundaries.get(currentRange.getIndexOfChromosome()).add(currentRange.getLower()); + listOfBoundaries.get(currentRange.getIndexOfChromosome()).add(currentRange.getUpper()); + } + + //we should have a list of sorted double values for each dimension now, containing all values existing in the dvl ranges + final List<Hypercube> hypercubes = new ArrayList<>(); + addVariationsOfNextDimension(dimensions, 0, new HypercubeBuilder(dimensions), listOfBoundaries, hypercubes); + + return hypercubes; + } + + private void addVariationsOfNextDimension(int dimensions, int indexOfNextDimension, HypercubeBuilder builder, List<SortedSet<Double>> listOfBoundaries, final List<Hypercube> result){ + if(indexOfNextDimension == dimensions) { + result.add(builder.build()); + return; + } + + final List<Double> boundaries = new ArrayList<>(listOfBoundaries.get(indexOfNextDimension)); + + for(int i = 0; i < boundaries.size() - 1; ++i) { + final HypercubeBuilder next = new HypercubeBuilder(builder); + next.append(boundaries.get(i), boundaries.get(i + 1)); + + addVariationsOfNextDimension(dimensions, indexOfNextDimension + 1, next, listOfBoundaries, result); + } + } + + @Override + public StatisticsWriter init(Instance configuration) { + return this; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/Hypercube.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/Hypercube.java new file mode 100644 index 0000000000000000000000000000000000000000..a34fb9a534b1972fdab3ff9f6a407d19ba9c7e6b --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/Hypercube.java @@ -0,0 +1,106 @@ +package de.evoal.core.main.statistics.rangeCorrelated; + +import java.util.ArrayList; +import java.util.List; + +import de.evoal.core.main.ea.functions.correlation.model.Range; +import de.evoal.core.api.properties.Properties; + +import smile.math.matrix.Matrix; + +public class Hypercube { + private final int dimensions; + private final Range[] definition; + private final List<Properties> data = new ArrayList<>(); + + public Hypercube(final Hypercube other) { + this.dimensions = other.dimensions; + this.definition = other.definition; + } + + public Hypercube(Range[] definition) { + this.definition = definition; + this.dimensions = definition.length; + } + + public Hypercube(final Range[] definition, int dimensions) { + if(definition.length != dimensions) { + throw new IllegalStateException("Hypercubedefinition does not match number of dimensions."); + } + + else { + this.definition = definition; + this.dimensions = dimensions; + } + } + + public boolean check(Properties dataPoint) { + if(dataPoint.size()!= dimensions) { + throw new IllegalStateException("Given dataPoint does not have the same number of dimensions as the hypercube"); + } + + else { + for(int i=0; i< dataPoint.size(); i++) { + if(!definition[i].includes(dataPoint.get(i))){ + return false; + } + } + return true; + } + } + + public void addDataPoint(final Properties dataPoint) { + if(check(dataPoint)) { + data.add(dataPoint); + } + } + + public Matrix computeCovarianceMatrix(){ + final int dimensions = this.dimensions; + final int dataSize = data.size(); + + if(dataSize == 0) { + return new Matrix(dimensions, dimensions); + } + + final double [] means = new double[dimensions]; + for(int i=0; i< dataSize; i++) { + for(int j=0; j< dimensions; j++) { + means[j] = means[j] + data.get(i).get(j)/(double)data.size(); + } + } + + + final Matrix covarianceMatrix = new Matrix(dimensions, dimensions); + for(int x = 0; x < dimensions; ++x) { + for(int y = 0; y < dimensions; ++y) { + double value = 0.0; + for(int t = 0; t < dataSize; ++t) { + value += ((data.get(t).get(x) - means[x])*(data.get(t).get(y) - means[y])); + } + value = value / dataSize; + covarianceMatrix.set(x, y, value); + } + } + + return covarianceMatrix; + } + + public double computeSquaredDistanceToCovarianceMatrix(Hypercube other) { + Matrix otherCovariance = other.computeCovarianceMatrix(); + Matrix thisCovariance = this.computeCovarianceMatrix(); + + Matrix difference = thisCovariance.sub(otherCovariance); + difference.mul(difference); + return difference.sum(); + + } + + public boolean equals(Hypercube other) { + return this.definition.equals(other.definition); + } + + public boolean contentEquals(Hypercube other) { + return this.definition.equals(other.definition) && this.data.equals(other.data); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/HypercubeBuilder.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/HypercubeBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..9ac150c063f8ab02967af3f860ea07d4ba59f71e --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/HypercubeBuilder.java @@ -0,0 +1,32 @@ +package de.evoal.core.main.statistics.rangeCorrelated; + +import de.evoal.core.main.ea.functions.correlation.model.Range; + +import java.util.LinkedList; +import java.util.List; + +public class HypercubeBuilder { + private final int dimensions; + private final List<Range> intervals = new LinkedList<>(); + + public HypercubeBuilder(int dimensions) { + this.dimensions = dimensions; + } + + public HypercubeBuilder(final HypercubeBuilder halfbuiltCube) { + this.dimensions = halfbuiltCube.dimensions; + this.intervals.addAll(halfbuiltCube.intervals); + } + + public Hypercube build() { + if(intervals.size() == dimensions) { + return new Hypercube(intervals.toArray(new Range [dimensions]), dimensions); + } + + else throw new IllegalStateException("Intervallist is not yet complete."); + } + + public void append(final double lower, final double upper) { + intervals.add(new Range(lower, upper)); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/Interval.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/Interval.java new file mode 100644 index 0000000000000000000000000000000000000000..a7fae3887a601c20d603619b1cfd2dc9b6e469b8 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/Interval.java @@ -0,0 +1,15 @@ +package de.evoal.core.main.statistics.rangeCorrelated; + +public class Interval { + private double lowerBound; + private double upperBound; + + public boolean check(double value) { + return (lowerBound <= value && value <= upperBound); + } + + public Interval(double lowerBound, double upperBound) { + this.lowerBound = lowerBound; + this.upperBound = upperBound; + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/PropertyRange.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/PropertyRange.java new file mode 100644 index 0000000000000000000000000000000000000000..269e082d399ca2db479bf60ffc66a59fe154fcb0 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/PropertyRange.java @@ -0,0 +1,29 @@ +package de.evoal.core.main.statistics.rangeCorrelated; + +import de.evoal.core.main.ea.functions.correlation.model.Range; + +public class PropertyRange { + // TODO FIXME AND SO ON + + + private final int indexOfChromosome; + private final Range range; + + public PropertyRange(final int indexOfChromosome, final Range range) { + this.indexOfChromosome = indexOfChromosome; + this.range = range; + } + + public int getIndexOfChromosome() { + return indexOfChromosome; + } + + public double getLower() { + return range.getLower(); + } + + public double getUpper() { + return range.getUpper(); + } +} + diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/Range.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/Range.java new file mode 100644 index 0000000000000000000000000000000000000000..80bc27dfb95b9f5c661eb129bbfb4d4b9bf370dc --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/rangeCorrelated/Range.java @@ -0,0 +1,3 @@ +package de.evoal.core.main.statistics.rangeCorrelated; + +record Range(double lower, double upper) {} \ No newline at end of file diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/writer/csv/CsvStrategy.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/writer/csv/CsvStrategy.java new file mode 100644 index 0000000000000000000000000000000000000000..cc12027222fa579c14c331e7e7ea3e1d312cad2f --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/writer/csv/CsvStrategy.java @@ -0,0 +1,49 @@ +package de.evoal.core.main.statistics.writer.csv; + +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.BlackboardValue; +import de.evoal.core.api.statistics.Column; +import de.evoal.core.api.statistics.Writer; +import de.evoal.core.api.statistics.WriterException; +import de.evoal.core.api.statistics.WriterStrategy; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.inject.Named; +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@ApplicationScoped +@Named("csv") +public class CsvStrategy extends WriterStrategy { + @Inject @BlackboardValue(BlackboardEntry.EVALUATION_OUTPUT_FOLDER) + private File outputFolder; + + private final Map<String, CsvWriter> writerMap = new HashMap<>(); + + @Override + public void close(final Writer writer) { + writer.flush(); + } + + @Override + public Writer create(final String name, final List<Column> header) throws WriterException { + if(writerMap.containsKey(name)) { + return writerMap.get(name); + } + + final File csvFile = new File(outputFolder, name + ".csv"); + csvFile.getParentFile().mkdirs(); + + final CsvWriter writer = new CsvWriter(csvFile, context, header); + writerMap.put(name, writer); + + return writer; + } + + protected void finalize() { + writerMap.values().forEach(Writer::close); + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/writer/csv/CsvWriter.java b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/writer/csv/CsvWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..9560ed8056db8278febed2caf13815f034c06eeb --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/de/evoal/core/main/statistics/writer/csv/CsvWriter.java @@ -0,0 +1,84 @@ +package de.evoal.core.main.statistics.writer.csv; + +import de.evoal.core.api.statistics.Column; +import de.evoal.core.api.statistics.Writer; +import de.evoal.core.api.statistics.WriterContext; +import de.evoal.core.api.statistics.WriterException; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.List; + +@Slf4j +public class CsvWriter implements Writer { + + private final CSVPrinter csvPrinter; + private final FileWriter csvWriter; + private final WriterContext context; + + public CsvWriter(final File filename, final WriterContext context, final List<Column> header) throws WriterException { + try { + final String[] fileHeader = new String[context.size() + header.size()]; + + int i = 0; + for(final Column column : context.getColumns()) { + fileHeader[i++] = column.getName(); + } + + for(final Column column : header) { + fileHeader[i++] = column.getName(); + } + + csvWriter = new FileWriter(filename); + csvPrinter = new CSVPrinter(csvWriter, + CSVFormat.DEFAULT + .withHeader(fileHeader)); + + this.context = context; + } catch(final IOException e) { + throw new WriterException("Unable to open CSV file: " + filename, e); + } + } + + @Override + public void addRecord(final Object[] data) throws WriterException { + System.err.println("Adding data to file."); + try { + for(final Column column : context.getColumns()) { + csvPrinter.print(context.get(column)); + } + + for(final Object obj : data) { + csvPrinter.print(obj); + } + + csvPrinter.println(); + } catch(final IOException e) { + throw new WriterException("Unable to write CSV file.", e); + } + } + + @Override + public void close() { + try { + csvPrinter.close(); + csvWriter.close(); + } catch (final IOException e) { + log.error("Failed to close CSV file.", e); + } + } + + @Override + public void flush() { + try { + csvPrinter.flush(); + csvWriter.flush(); + } catch (final IOException e) { + log.error("Failed to close CSV file.", e); + } + } +} diff --git a/src/core/de.evoal.core.main/src/main/java/module-info.java b/src/core/de.evoal.core.main/src/main/java/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..46cf1d7e58f7fc5a744ba3428d1315c19fdfbe86 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/java/module-info.java @@ -0,0 +1,59 @@ +module de.evoal.core.main { + requires jakarta.enterprise.cdi.api; + requires jakarta.inject.api; + + requires java.annotation; + requires java.base; + + requires lombok; + requires org.slf4j; + + requires weld.se.core; + requires deltaspike.cdictrl.api; + requires deltaspike.cdictrl.weld; + requires deltaspike.core.api; + + requires org.eclipse.emf.common; + requires org.eclipse.emf.ecore; + requires org.eclipse.xtext; + + requires io.jenetics.base; + requires commons.math3; + requires commons.csv; + requires smile.math; + + requires com.fasterxml.jackson.databind; + requires io.jenetics.ext; + requires decimal4j; + requires guice; + + requires de.evoal.languages.model.dl.dsl; + requires de.evoal.languages.model.dl; + requires de.evoal.languages.model.eal; + requires de.evoal.languages.model.eal.dsl; + requires de.evoal.languages.model.el; + requires de.evoal.languages.model.el.dsl; + requires de.evoal.languages.model.instance; + + requires de.evoal.core.api; + requires de.evoal.languages.model.ddl; + + opens de.evoal.core.main.cdi.producer to weld.core.impl; + opens de.evoal.core.main.ddl.correlation to weld.core.impl; + opens de.evoal.core.main.ddl.constraint to weld.core.impl; + opens de.evoal.core.main.ddl.constraint.strategies to weld.core.impl; + opens de.evoal.core.main.ddl.constraint.strategies.calculations to weld.core.impl; + opens de.evoal.core.main.ddl.constraint.strategies.constraint to weld.core.impl; + opens de.evoal.core.main.ddl.constraint.strategies.fitness to weld.core.impl; + opens de.evoal.core.main.ddl.deviation to weld.core.impl; + opens de.evoal.core.main.ea.alterer to weld.core.impl; + opens de.evoal.core.main.ea.alterer.mutator to weld.core.impl; + opens de.evoal.core.main.search to weld.core.impl; + opens de.evoal.core.main.statistics to weld.core.impl; + opens de.evoal.core.main.statistics.constraint to weld.core.impl; + opens de.evoal.core.main.statistics.fitness to weld.core.impl; + opens de.evoal.core.main.statistics.individuals to weld.core.impl; + opens de.evoal.core.main.statistics.nop to weld.core.impl; + opens de.evoal.core.main.statistics.rangeCorrelated to weld.core.impl; + opens de.evoal.core.main.statistics.writer.csv to weld.core.impl; +} diff --git a/src/core/de.evoal.core.main/src/main/resources/META-INF/MANIFEST.MF b/src/core/de.evoal.core.main/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000000000000000000000000000000..9d885be534121a9f146924f4832955dfe2ee2d4b --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1 @@ +Manifest-Version: 1.0 diff --git a/src/core/de.evoal.core.main/src/main/resources/META-INF/beans.xml b/src/core/de.evoal.core.main/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000000000000000000000000000000..36964de4bdc1dbcbbf02c9552692e62804947dc3 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/resources/META-INF/beans.xml @@ -0,0 +1,11 @@ +<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" + bean-discovery-mode="annotated" + version="2.0"> + <scan> + <exclude name="org.apache.deltaspike.core.impl.throttling.ThrottledInterceptor" /> + <exclude name="org.apache.deltaspike.core.impl.scope.window.DefaultWindowContextQuotaHandler" /> + <exclude name="org.apache.deltaspike.core.impl.scope.window.WindowBeanHolder" /> + </scan> +</beans> \ No newline at end of file diff --git a/src/core/de.evoal.core.main/src/main/resources/logback.xml b/src/core/de.evoal.core.main/src/main/resources/logback.xml new file mode 100644 index 0000000000000000000000000000000000000000..94e55be2c26acd3500962d34eaa3d74732293502 --- /dev/null +++ b/src/core/de.evoal.core.main/src/main/resources/logback.xml @@ -0,0 +1,13 @@ +<configuration> + <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> + </encoder> + </appender> + + <logger name="de.evoal" level="INFO" /> + + <root level="WARN"> + <appender-ref ref="STDOUT" /> + </root> +</configuration> \ No newline at end of file diff --git a/src/core/de.evoal.core.releng.parent/pom.xml b/src/core/de.evoal.core.releng.parent/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..a23b50a98035af19b390de40a3740e6390deb177 --- /dev/null +++ b/src/core/de.evoal.core.releng.parent/pom.xml @@ -0,0 +1,211 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>de.evoal.core</groupId> + <artifactId>releng.parent</artifactId> + <version>0.9.0-SNAPSHOT</version> + <packaging>pom</packaging> + + <name>EvoAl - Core - Parent</name> + + <properties> + <maven.compiler.source>17</maven.compiler.source> + <maven.compiler.target>17</maven.compiler.target> + + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + + <deltaspike.version>1.9.6</deltaspike.version> + <evoal.languages.version>1.0.0-SNAPSHOT</evoal.languages.version> + <jackson.version>2.13.4</jackson.version> + <jenetics.version>7.1.0</jenetics.version> + <lombok.version>1.18.24</lombok.version> + <slf4j.api.version>2.0.0</slf4j.api.version> + <smile.version>2.6.0</smile.version> + </properties> + + <modules> + <module>../de.evoal.core.api</module> + <module>../de.evoal.core.main</module> + <module>../de.evoal.generator.main</module> + <module>../de.evoal.surrogate.api</module> + <module>../de.evoal.surrogate.simple</module> + <module>../de.evoal.surrogate.svr</module> +<!-- <module>../de.evoal.surrogate.neural</module> --> + </modules> + + <dependencies> + <!-- CDI APIs --> + <dependency> + <groupId>jakarta.inject</groupId> + <artifactId>jakarta.inject-api</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>jakarta.enterprise</groupId> + <artifactId>jakarta.enterprise.cdi-api</artifactId> + <version>2.0.2</version> + <scope>provided</scope> + </dependency> + + <!-- Logging API --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>${slf4j.api.version}</version> + <scope>provided</scope> + </dependency> + + <!-- Prevent me from typing --> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <scope>provided</scope> + </dependency> + </dependencies> + + <dependencyManagement> + <dependencies> + <!-- CDI APIs --> + <dependency> + <groupId>jakarta.inject</groupId> + <artifactId>jakarta.inject-api</artifactId> + <version>1.0</version> + </dependency> + + <dependency> + <groupId>jakarta.enterprise</groupId> + <artifactId>jakarta.enterprise.cdi-api</artifactId> + <version>2.0.2</version> + </dependency> + + <dependency> + <groupId>javax.annotation</groupId> + <artifactId>javax.annotation-api</artifactId> + <version>1.3.2</version> + </dependency> + + <!-- Logging API --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>${slf4j.api.version}</version> + </dependency> + + <!-- Prevent me from typing --> + <dependency> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>${lombok.version}</version> + <scope>provided</scope> + </dependency> + + <!-- Xtext --> + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.ecore</artifactId> + <version>2.25.0</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.common</artifactId> + <version>2.25.0</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.eclipse.xtext</groupId> + <artifactId>org.eclipse.xtext</artifactId> + <version>2.25.0</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.antlr</groupId> + <artifactId>antlr-runtime</artifactId> + <version>3.2</version> + </dependency> + + + <!-- + + <!- - Unit Testing - -> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.8.2</version> + <scope>test</scope> + </dependency> + --> + + <dependency> + <groupId>de.evoal.core</groupId> + <artifactId>core.api</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + </dependencyManagement> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.2.2</version> + </plugin> + </plugins> + + <pluginManagement> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.10.1</version> + <configuration> + <forceJavacCompilerUse>true</forceJavacCompilerUse> + <annotationProcessorPaths> + <path> + <groupId>org.projectlombok</groupId> + <artifactId>lombok</artifactId> + <version>${lombok.version}</version> + </path> + </annotationProcessorPaths> + </configuration> + </plugin> + </plugins> + </pluginManagement> + </build> + + <distributionManagement> + <repository> + <id>gitlab-maven</id> + <name>Stable Releases</name> + <url>${env.CI_API_V4_URL}/projects/${env.CI_PROJECT_ID}/packages/maven</url> + </repository> + + <snapshotRepository> + <id>gitlab-maven</id> + <name>Internal Snapshots</name> + <url>${env.CI_API_V4_URL}/projects/${env.CI_PROJECT_ID}/packages/maven</url> + </snapshotRepository> + </distributionManagement> + + <repositories> + <repository> + <id>evoal-artifacts</id> + <name>EvoAl - Artifacts</name> + <url>https://gitlab.informatik.uni-bremen.de/api/v4/projects/30380/packages/maven</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + </repositories> + +</project> diff --git a/src/core/de.evoal.generator.main/pom.xml b/src/core/de.evoal.generator.main/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..7aeb1b9c2886ebda93009835e03d4b7b1f040cbc --- /dev/null +++ b/src/core/de.evoal.generator.main/pom.xml @@ -0,0 +1,138 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>de.evoal.core</groupId> + <artifactId>releng.parent</artifactId> + <version>0.9.0-SNAPSHOT</version> + + <relativePath>../de.evoal.core.releng.parent</relativePath> + </parent> + + <artifactId>generator.main</artifactId> + <name>EvoAl - Generator - Main</name> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>core.api</artifactId> + <scope>provided</scope> + </dependency> + + <!-- DSL dependencies --> + <!-- Xtext --> + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.ecore</artifactId> + </dependency> + + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.common</artifactId> + </dependency> + + <dependency> + <groupId>org.eclipse.xtext</groupId> + <artifactId>org.eclipse.xtext</artifactId> + </dependency> + + <!-- Math libraries --> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-math3</artifactId> + <version>3.6.1</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.ddl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.dl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.el</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.generator</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.instance</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.ddl.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.dl.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.el.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.generator.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>copy-dependencies</goal> + </goals> + <configuration> + <outputDirectory>${project.build.directory}/${project.artifactId}-dependencies</outputDirectory> + <includeScope>runtime</includeScope> + <excludeScope>provided</excludeScope> + <excludeTransitive>true</excludeTransitive> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile> + <manifest> + <addClasspath>true</addClasspath> + <classpathPrefix>${project.artifactId}-dependencies/</classpathPrefix> + </manifest> + </archive> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/api/AbstractGeneratorFunction.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/api/AbstractGeneratorFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..a685db2967ae81ad43ef9011b7362f7db3b0b3c8 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/api/AbstractGeneratorFunction.java @@ -0,0 +1,70 @@ +package de.evoal.generator.api; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.languages.model.ddl.DataDescription; +import de.evoal.languages.model.generator.Step; +import de.evoal.languages.model.instance.DataReference; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +public abstract class AbstractGeneratorFunction implements GeneratorFunction { + + protected Step configuration; + + /** + * The properties the generator function writes. + */ + protected PropertiesSpecification writeSpecification; + + /** + * The properties the generator function reads. + */ + protected PropertiesSpecification readSpecification; + + @Override + public GeneratorFunction init(final Step configuration) { + this.configuration = configuration; + + writeSpecification = createSpecification(configuration.getWrites()); + readSpecification = createSpecification(configuration.getReads()); + + log.info("Reading {}", readSpecification); + log.info("Writing {}", writeSpecification); + + return this; + } + + private static PropertiesSpecification createSpecification(final List<DataReference> references) { + final PropertiesSpecification.Builder builder = PropertiesSpecification.builder(); + + builder.add(references.stream() + .map(DataReference::getDefinition) + .map(DataDescription::getName)); + + return builder.build(); + } + + /** + * Merges the properties' specification with the write-specicfication of this + * generator function, creates a new properties instance according to the new + * specification and copies all property values. + */ + protected Properties mergeAndCopy(final Properties properties) { + final PropertiesSpecification specification = + PropertiesSpecification.builder() + .add(properties.getSpecification()) + .add(writeSpecification) + .build(); + + final Properties result = new Properties(specification); + for(final PropertySpecification ps : properties.getSpecification().getProperties()) { + result.put(ps, properties.get(ps)); + } + + return result; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/api/GeneratorBlackboardEntry.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/api/GeneratorBlackboardEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..1430b3747ff62e05158e8782ae7cb11c66884792 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/api/GeneratorBlackboardEntry.java @@ -0,0 +1,15 @@ +package de.evoal.generator.api; + +public final class GeneratorBlackboardEntry { + /** + * Loaded generator configuration. + */ + public static final String GENERATOR_CONFIGURATION = "generator:configuration"; + + /** + * Configuration file for the data generator. + */ + public static final String GENERATOR_CONFIGURATION_FILE = "generator:configuration-file"; + + private GeneratorBlackboardEntry() {} +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/api/GeneratorFunction.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/api/GeneratorFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..fa25d8fd24ce733402983e352d92a5a545f5f3db --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/api/GeneratorFunction.java @@ -0,0 +1,15 @@ +package de.evoal.generator.api; + + +import de.evoal.core.api.properties.Properties; +import de.evoal.languages.model.generator.Step; + +/** + * Interface for all generator functions. A generator function is a named CDI + * component with a pre-defined life-cycle. + */ +public interface GeneratorFunction { + GeneratorFunction init(final Step configuration); + + public Properties apply(final Properties in); +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/DataGenerator.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/DataGenerator.java new file mode 100644 index 0000000000000000000000000000000000000000..2dee16b414624cffc8d23f2c3343213809ed0486 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/DataGenerator.java @@ -0,0 +1,74 @@ +package de.evoal.generator.main; + +import java.io.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.inject.Named; + +import de.evoal.core.api.cdi.BlackboardValue; +import de.evoal.core.api.cdi.MainClass; +import de.evoal.generator.api.*; +import de.evoal.generator.main.generators.GeneratorFactory; +import de.evoal.generator.main.internal.Pipeline; +import de.evoal.generator.main.internal.StatementExecutor; +import de.evoal.languages.model.generator.Configuration; +import de.evoal.languages.model.generator.PipelineDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Main class for EvoAl's data generator. + */ +@ApplicationScoped +@Named("data-generator") +public class DataGenerator implements MainClass { + + /** + * Logger instance + */ + private final static Logger log = LoggerFactory.getLogger(DataGenerator.class); + + @Inject + @BlackboardValue(GeneratorBlackboardEntry.GENERATOR_CONFIGURATION) + private Configuration configuration; + + @Inject + private GeneratorFactory factory; + + @Override + public void run() { + log.info("Starting data generation."); + + final Map<String, Pipeline> pipelineTable = createPipelines(); + + new StatementExecutor(pipelineTable) + .execute(configuration.getStatements()); + + log.info("Finished data generation."); + } + + private Map<String, Pipeline> createPipelines() { + final Map<String, Pipeline> result = new HashMap<>(); + + for(final PipelineDefinition definition : configuration.getPipelines()) { + final String pipelineName = definition.getName(); + + log.info("Creating pipeline '{}'.", pipelineName); + + final Pipeline pipeline = new Pipeline(pipelineName, + definition.getSteps() + .stream() + .map(factory::create) + .collect(Collectors.toList())); + + result.put(pipelineName, pipeline); + } + + return result; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/Ackley.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/Ackley.java new file mode 100644 index 0000000000000000000000000000000000000000..6cca3ee9d8f15408bd3ce24089b7880848227ca5 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/Ackley.java @@ -0,0 +1,57 @@ +package de.evoal.generator.main.benchmarks; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.generator.api.AbstractGeneratorFunction; +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.generator.main.utils.ELHelper; +import de.evoal.languages.model.generator.Step; +import lombok.extern.slf4j.Slf4j; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("ackley") +@Slf4j +public class Ackley extends AbstractGeneratorFunction { + private double a = 20; + + private double b = 0.2; + + private double c = 6.283185307179586; + + @Override + public Properties apply(final Properties in) { + final Properties result = mergeAndCopy(in); + + final double n = readSpecification.size(); + + double sum1 = 0.0; + double sum2 = 0.0; + + for(final PropertySpecification ps : readSpecification.getProperties()) { + double read_i = in.get(ps); + + sum1 += Math.pow(read_i, 2.0); + sum2 += Math.cos(c * read_i); + } + + double value = -a * Math.exp(-b * Math.sqrt((1 / n) * sum1)) - Math.exp((1 / n) * sum2) + a + Math.E; + + result.put(writeSpecification.getProperties().get(0), value); + + return result; + } + + @Override + public GeneratorFunction init(final Step configuration) { + super.init(configuration); + + a = ELHelper.readDouble(configuration.getInstance(), "a"); + b = ELHelper.readDouble(configuration.getInstance(), "b"); + c = ELHelper.readDouble(configuration.getInstance(), "c"); + + return this; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/Rastrigin.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/Rastrigin.java new file mode 100644 index 0000000000000000000000000000000000000000..c5538d06ed427fe8a72fe0cc14f574337ca53e0e --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/Rastrigin.java @@ -0,0 +1,45 @@ +package de.evoal.generator.main.benchmarks; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.generator.api.AbstractGeneratorFunction; +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.languages.model.el.DoubleLiteral; +import de.evoal.languages.model.generator.Step; +import de.evoal.languages.model.instance.LiteralValue; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("rastrigin") +public class Rastrigin extends AbstractGeneratorFunction { + private double a; + + @Override + public Properties apply(final Properties in) { + final Properties result = mergeAndCopy(in); + + final double n = readSpecification.size(); + double value = a * n; + + for(final PropertySpecification ps : readSpecification.getProperties()) { + double read_i = in.get(ps); + + value += (Math.pow(read_i, 2.0) - a * Math.cos(2 * Math.PI * read_i)); + } + + result.put(writeSpecification.getProperties().get(0), value); + + return result; + } + + @Override + public GeneratorFunction init(final Step configuration) { + super.init(configuration); + + a = ((DoubleLiteral)((LiteralValue)configuration.getInstance().findAttribute("a").getValue()).getLiteral()).getValue(); + + return this; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/Rosenbrock.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/Rosenbrock.java new file mode 100644 index 0000000000000000000000000000000000000000..9e1b291fd2087d1a9ef485449537986b0ff2f0a9 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/Rosenbrock.java @@ -0,0 +1,34 @@ +package de.evoal.generator.main.benchmarks; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.generator.api.AbstractGeneratorFunction; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("rosenbrock") +public class Rosenbrock extends AbstractGeneratorFunction { + @Override + public Properties apply(final Properties in) { + final Properties result = mergeAndCopy(in); + + final double n = readSpecification.size(); + double value = 0.0; + + for(int i = 0; i < readSpecification.getProperties().size() - 1; ++i) { + final PropertySpecification ps_i = readSpecification.getProperties().get(i); + final PropertySpecification ps_n = readSpecification.getProperties().get(i+1); + + double read_i = in.get(ps_i); + double read_n = in.get(ps_n); + + value += 100 * Math.pow((Math.pow(read_i, 2.0) - read_n), 2.0) + Math.pow(1 - read_i, 2.0); + } + + result.put(writeSpecification.getProperties().get(0), value); + + return result; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/WeightedSphere.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/WeightedSphere.java new file mode 100644 index 0000000000000000000000000000000000000000..f59a161383b96902909a6adc4f114984ca9be41f --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/benchmarks/WeightedSphere.java @@ -0,0 +1,31 @@ +package de.evoal.generator.main.benchmarks; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.generator.api.AbstractGeneratorFunction; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("weighted-sphere") +public class WeightedSphere extends AbstractGeneratorFunction { + @Override + public Properties apply(final Properties in) { + final Properties result = mergeAndCopy(in); + + final double n = readSpecification.size(); + double value = 0.0; + int counter = 0; + + for(final PropertySpecification ps : readSpecification.getProperties()) { + double read_i = in.get(ps); + + value += Math.pow(read_i, 2.0) * Math.pow(counter++, 2.0); + } + + result.put(writeSpecification.getProperties().get(0), value); + + return result; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/cdi/GeneratorConfigurationProducer.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/cdi/GeneratorConfigurationProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..db9f87b33fbcd8b753da26aa53d780b35c3e6ba5 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/cdi/GeneratorConfigurationProducer.java @@ -0,0 +1,127 @@ +package de.evoal.generator.main.cdi; + +import com.google.inject.Injector; + +import de.evoal.core.api.board.Blackboard; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.BlackboardValue; +import de.evoal.generator.api.GeneratorBlackboardEntry; +import de.evoal.languages.model.ddl.dsl.DataDescriptionLanguageStandaloneSetup; +import de.evoal.languages.model.ddl.impl.DdlPackageImpl; +import de.evoal.languages.model.dl.dsl.DefinitionLanguageStandaloneSetup; +import de.evoal.languages.model.dl.impl.DlPackageImpl; +import de.evoal.languages.model.el.dsl.ExpressionLanguageStandaloneSetup; +import de.evoal.languages.model.el.impl.ELPackageImpl; +import de.evoal.languages.model.generator.dsl.GeneratorDSLStandaloneSetup; +import de.evoal.languages.model.generator.Configuration; +import de.evoal.languages.model.generator.impl.GeneratorPackageImpl; +import lombok.extern.slf4j.Slf4j; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; +import java.io.File; +import java.util.Optional; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.resource.XtextResourceSet; + +@ApplicationScoped +@Slf4j +public class GeneratorConfigurationProducer { + public void loadModel(final @Observes BlackboardEntry value, final Blackboard board) { + if(!GeneratorBlackboardEntry.GENERATOR_CONFIGURATION_FILE.equals(value.getLabel())) { + return; + } + + final String configurationFile = board.get(value.getLabel()); + + log.info("Using generator configuration from '{}'.", configurationFile); + + initializeEMF(); + + final File file = new File(configurationFile); + + if(!file.isFile()) { + log.info("Configured generator configuration is a folder."); + throw new IllegalArgumentException("Please specify a generator file."); + } + + if(!file.canRead()) { + log.info("Configured generator configuration cannot be read."); + throw new IllegalArgumentException("Please specify a readable genrator file."); + } + + final Configuration configuration = read(file).get(); + board.bind(GeneratorBlackboardEntry.GENERATOR_CONFIGURATION, configuration); + } + + /** + * Initialize the model packages and perform the parser setup. + */ + private void initializeEMF() { + DdlPackageImpl.init(); + ELPackageImpl.init(); + DlPackageImpl.init(); + GeneratorPackageImpl.init(); + + DataDescriptionLanguageStandaloneSetup.doSetup(); + ExpressionLanguageStandaloneSetup.doSetup(); + DefinitionLanguageStandaloneSetup.doSetup(); + GeneratorDSLStandaloneSetup.doSetup(); + } + + /** + * Parses the given generator file and returns the corresponding model. + * + * @param modelFile The model file to read. + * @return The data validation model or an empty optional. + */ + private Optional<Configuration> read(final File modelFile) { + log.info("Reading model file {}.", modelFile); + + final Injector injector = new GeneratorDSLStandaloneSetup().createInjectorAndDoEMFRegistration(); + final XtextResourceSet resourceSet = injector.getInstance(XtextResourceSet.class); + resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE); + resourceSet.addLoadOption(XtextResource.OPTION_ENCODING, "UTF-8"); + + try { + final URI modelURI = URI.createFileURI(modelFile.getAbsolutePath()); + final Resource resource = resourceSet.getResource(modelURI, true); + resource.load(resourceSet.getLoadOptions()); + + if(!resource.getErrors().isEmpty()) { + for(Resource.Diagnostic diagnostic : resource.getErrors()) { + log.error("Error while processing rule '{}': {}", modelFile, diagnostic); + } + } + + if(!resource.getWarnings().isEmpty()) { + for(Resource.Diagnostic diagnostic : resource.getWarnings()) { + log.error("Warning while processing rule '{}': {}", modelFile, diagnostic); + } + } + + return Optional.of((Configuration) resource.getContents().get(0)); + } catch (final Exception e) { + log.error("Unable to to generator file '{}'.", modelFile, e); + return Optional.empty(); + } + } + + @Produces + @BlackboardValue(GeneratorBlackboardEntry.GENERATOR_CONFIGURATION) + public Configuration injectIntegerValue(final InjectionPoint ip, final Blackboard board) { + final BlackboardValue value = ip.getAnnotated().getAnnotation(BlackboardValue.class); + final Object result = board.get(value.value()); + + if(result instanceof Configuration) { + return (Configuration)result; + } + + throw new IllegalArgumentException("Unable to handle type " + result.getClass()); + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/functions/ConstantFunction.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/functions/ConstantFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..a6d0834c852131370a8ee968614adb821e136a0b --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/functions/ConstantFunction.java @@ -0,0 +1,45 @@ +package de.evoal.generator.main.functions; + +import de.evoal.core.api.properties.Properties; +import de.evoal.generator.api.AbstractGeneratorFunction; +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.languages.model.el.DoubleLiteral; +import de.evoal.languages.model.generator.Step; +import de.evoal.languages.model.instance.Array; +import de.evoal.languages.model.instance.LiteralValue; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("constants") +public class ConstantFunction extends AbstractGeneratorFunction { + private double [] constants = {}; + + public Properties apply(final Properties in) { + final Properties result = mergeAndCopy(in); + + for(int i = 0; i < constants.length; ++i) { + result.put(writeSpecification.getProperties().get(i), constants[i]); + } + + return result; + } + + @Override + public GeneratorFunction init(final Step configuration) { + super.init(configuration); + + final Array constantsArray = (Array)configuration.getInstance().findAttribute("constants").getValue(); + + constants = constantsArray.getValues() + .stream() + .map(LiteralValue.class::cast) + .map(LiteralValue::getLiteral) + .map(DoubleLiteral.class::cast) + .mapToDouble(DoubleLiteral::getValue) + .toArray(); + + return this; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/functions/NormalNoiseFunction.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/functions/NormalNoiseFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..f6e176c61518ed49fddff8f2e32f6ea2643612b9 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/functions/NormalNoiseFunction.java @@ -0,0 +1,53 @@ +package de.evoal.generator.main.functions; + +import java.util.ArrayList; +import java.util.List; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.generator.api.AbstractGeneratorFunction; +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.generator.main.utils.ELHelper; +import de.evoal.languages.model.generator.Step; +import org.apache.commons.math3.distribution.NormalDistribution; +import org.apache.commons.math3.distribution.RealDistribution; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("normally-distributed-noise") +public class NormalNoiseFunction extends AbstractGeneratorFunction { + + /** + * The different distributions to apply. + */ + private List<RealDistribution> distributions = new ArrayList<>(); + + public GeneratorFunction init(final Step configuration) { + super.init(configuration); + + ELHelper.readDistributions(configuration.getInstance(), "distributions") + .stream() + .map(d -> new NormalDistribution(d.μ(), d.σ())) + .forEach(distributions::add); + + return this; + } + + @Override + public Properties apply(final Properties in) { + final Properties result = mergeAndCopy(in); + final int dimension = writeSpecification.size(); + + for(int i = 0; i < dimension; ++i) { + final PropertySpecification ps = writeSpecification.getProperties().get(i); + + double value = result.get(ps) + distributions.get(i).sample(); + + result.put(ps, value); + } + + return result; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/GeneratorFactory.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/GeneratorFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..88af80bd36735ef6ca21252dd38c31ab82a4b77f --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/GeneratorFactory.java @@ -0,0 +1,17 @@ +package de.evoal.generator.main.generators; + +import de.evoal.core.api.cdi.BeanFactory; +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.languages.model.generator.Step; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class GeneratorFactory { + public GeneratorFunction create(final Step configuration) { + final String functionName = configuration.getInstance().getName().getName(); + + return BeanFactory.create(functionName, GeneratorFunction.class) + .init(configuration); + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/MultivariateNormalDistribution.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/MultivariateNormalDistribution.java new file mode 100644 index 0000000000000000000000000000000000000000..a32c2e66264e5b4f40788b581017e70d07073839 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/MultivariateNormalDistribution.java @@ -0,0 +1,56 @@ +package de.evoal.generator.main.generators; + +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.languages.model.el.DoubleLiteral; +import de.evoal.languages.model.generator.Step; +import de.evoal.languages.model.instance.*; +import de.evoal.languages.model.instance.impl.InstanceImpl; +import lombok.NonNull; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Named("multivariate-normal-distribution") +@Dependent +public class MultivariateNormalDistribution extends MultivariateRealDistributionBase { + + @Override + public GeneratorFunction init(final Step configuration) { + super.init(configuration); + + double [] means = readMeans(configuration.getInstance()); + double [][] covariance = readCovariance(configuration.getInstance()); + + setDistribution(new org.apache.commons.math3.distribution.MultivariateNormalDistribution(means, covariance)); + + return this; + } + + private double[][] readCovariance(final @NonNull Instance instance) { + final Array array = (Array) instance.findAttribute("covariances") + .getValue(); + + return array.getValues() + .stream() + .map(Array.class::cast) + .map(this::readArray) + .toArray(i -> new double[i][]); + } + + private double []readArray(final @NonNull Array value) { + return value.getValues() + .stream() + .mapToDouble(this::readDouble) + .toArray(); + } + + private double readDouble(final Value value) { + return ((DoubleLiteral)((LiteralValue)value).getLiteral()).getValue(); + } + + private double[] readMeans(final @NonNull Instance instance) { + final Attribute meansAttribute = instance.findAttribute("means"); + + return readArray((Array)meansAttribute.getValue()); + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/MultivariateRealDistributionBase.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/MultivariateRealDistributionBase.java new file mode 100644 index 0000000000000000000000000000000000000000..43cc9847e4e51f24dcd5927d9caa76fc324548d8 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/MultivariateRealDistributionBase.java @@ -0,0 +1,30 @@ +package de.evoal.generator.main.generators; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.generator.api.AbstractGeneratorFunction; +import lombok.AccessLevel; +import lombok.Setter; +import org.apache.commons.math3.distribution.MultivariateRealDistribution; + +import java.util.List; + +public abstract class MultivariateRealDistributionBase extends AbstractGeneratorFunction { + @Setter(AccessLevel.PROTECTED) + private MultivariateRealDistribution distribution; + + @Override + public Properties apply(final Properties in) { + final Properties result = mergeAndCopy(in); + final List<PropertySpecification> specifications = writeSpecification.getProperties(); + final int dimension = specifications.size(); + + final double [] values = distribution.sample(); + + for(int i = 0; i < dimension; ++i) { + result.put(specifications.get(i), values[i]); + } + + return result; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/MultivariateUniformDistribution.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/MultivariateUniformDistribution.java new file mode 100644 index 0000000000000000000000000000000000000000..db6d82ad5bc4f157c96f054a1dec8f8ff11310cb --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/MultivariateUniformDistribution.java @@ -0,0 +1,50 @@ +package de.evoal.generator.main.generators; + +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.languages.model.el.DoubleLiteral; +import de.evoal.languages.model.generator.Step; +import de.evoal.languages.model.instance.Array; +import de.evoal.languages.model.instance.Instance; +import de.evoal.languages.model.instance.LiteralValue; + +import javax.inject.Named; +import java.util.List; +import java.util.stream.Collectors; + +//@Named("multivariate-uniform-distribution") +public class MultivariateUniformDistribution extends MultivariateRealDistributionBase { + private record Range(double lowerBound, double upperBound) { + } + + @Override + public GeneratorFunction init(final Step configuration) { + super.init(configuration); + + final List<Range> ranges = readRanges(configuration); + + double [] lhs = ranges.stream().mapToDouble(Range::lowerBound).toArray(); + double [] rhs = ranges.stream().mapToDouble(Range::upperBound).toArray(); + + // setDistribution(new org.apache.commons.math3.distribution.MultivariateRealDistribution(lhs, rhs)); + + return this; + } + + private List<Range> readRanges(final Step configuration) { + final Array ranges = (Array)configuration.getInstance().findAttribute("ranges"); + return ranges.getValues() + .stream() + .map(Instance.class::cast) + .map(this::readRange) + .collect(Collectors.toUnmodifiableList()); + } + + public Range readRange(final Instance value) { + return new Range(readDouble(value, "lower-bound"), readDouble(value, "upper-bound")); + } + + private double readDouble(final Instance value, final String name) { + return ((DoubleLiteral)((LiteralValue)value.findAttribute(name)).getLiteral()).getValue(); + } + +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/NormalDistribution.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/NormalDistribution.java new file mode 100644 index 0000000000000000000000000000000000000000..cebb3812d840ab9bf8266ff9da2d1449b9857be4 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/NormalDistribution.java @@ -0,0 +1,28 @@ +package de.evoal.generator.main.generators; + +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.languages.model.el.DoubleLiteral; +import de.evoal.languages.model.generator.Step; +import de.evoal.languages.model.instance.LiteralValue; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("normal-distribution") +public class NormalDistribution extends RealDistributionBase { + + @Override + public GeneratorFunction init(final Step configuration) { + super.init(configuration); + + double μ = ((DoubleLiteral)((LiteralValue)configuration.getInstance().findAttribute("μ").getValue()).getLiteral()).getValue(); + double σ = ((DoubleLiteral)((LiteralValue)configuration.getInstance().findAttribute("σ").getValue()).getLiteral()).getValue(); + + for(int i = 0; i < writeSpecification.getProperties().size(); ++i) { + getDistributions().add(new org.apache.commons.math3.distribution.NormalDistribution(μ, σ)); + } + + return this; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/RealDistributionBase.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/RealDistributionBase.java new file mode 100644 index 0000000000000000000000000000000000000000..8ca008a339d1814d973c365350667515e8a900d0 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/RealDistributionBase.java @@ -0,0 +1,29 @@ +package de.evoal.generator.main.generators; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.generator.api.AbstractGeneratorFunction; +import lombok.AccessLevel; +import lombok.Getter; +import org.apache.commons.math3.distribution.RealDistribution; + +import java.util.ArrayList; +import java.util.List; + +public abstract class RealDistributionBase extends AbstractGeneratorFunction { + @Getter(AccessLevel.PROTECTED) + private List<RealDistribution> distributions = new ArrayList<>(); + + @Override + public Properties apply(final Properties in) { + final Properties result = mergeAndCopy(in); + final List<PropertySpecification> specifications = writeSpecification.getProperties(); + final int dimension = specifications.size(); + + for(int i = 0; i < dimension; ++i) { + result.put(specifications.get(i), distributions.get(i).sample()); + } + + return result; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/UniformDistribution.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/UniformDistribution.java new file mode 100644 index 0000000000000000000000000000000000000000..fa01b96fb9c6fa073f151a76ca28e723eb710b85 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/generators/UniformDistribution.java @@ -0,0 +1,28 @@ +package de.evoal.generator.main.generators; + +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.languages.model.el.DoubleLiteral; +import de.evoal.languages.model.generator.Step; +import de.evoal.languages.model.instance.LiteralValue; +import org.apache.commons.math3.distribution.UniformRealDistribution; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("uniform-distribution") +public class UniformDistribution extends RealDistributionBase { + + public GeneratorFunction init(final Step configuration) { + super.init(configuration); + + Object ranges = ((DoubleLiteral)((LiteralValue)configuration.getInstance().findAttribute("μ").getValue()).getLiteral()).getValue(); + if(true) throw new IllegalStateException("Not yet implemented."); + + for(int i = 0; i < writeSpecification.getProperties().size(); ++i) { + getDistributions().add(new UniformRealDistribution(-1.0, 1.0)); + } + + return this; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/Pipeline.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/Pipeline.java new file mode 100644 index 0000000000000000000000000000000000000000..6dbdae8f2f5207f5eaa9159bbeb5eb0bc865158b --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/Pipeline.java @@ -0,0 +1,19 @@ +package de.evoal.generator.main.internal; + +import de.evoal.generator.api.GeneratorFunction; +import lombok.Getter; + +import java.util.List; + +public class Pipeline { + @Getter + private final String name; + + @Getter + private final List<GeneratorFunction> steps; + + public Pipeline(final String name, final List<GeneratorFunction> steps) { + this.name = name; + this.steps = steps; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/StatementExecutor.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/StatementExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..47ed93c6e35d76dcd6b36da213670c06883a4c87 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/StatementExecutor.java @@ -0,0 +1,168 @@ +package de.evoal.generator.main.internal; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.io.PropertiesWriter; +import de.evoal.generator.api.GeneratorFunction; +import de.evoal.languages.model.generator.*; +import de.evoal.languages.model.generator.util.GeneratorSwitch; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +@Slf4j +public class StatementExecutor extends GeneratorSwitch<Object> { + + /** + * Variable pattern + */ + private final Pattern varPattern = Pattern.compile("\\$\\{[^}]*}"); + + private final Map<String, Pipeline> pipelineTable; + + private SymbolTable symbols; + + public StatementExecutor(final Map<String, Pipeline> pipelineTable) { + this.pipelineTable = pipelineTable; + + this.symbols = new SymbolTable(null, (Map<String, Object>)(Map<String, ?>)pipelineTable); + } + + public void execute(final List<Statement> statements) { + statements.forEach(this::doSwitch); + } + + @Override + public Object caseForStatement(final ForStatement forLoop) { + final String varName = forLoop.getName(); + + final SymbolTable currentSymbols = this.symbols; + + final Stream<?> loopElements = (Stream<?>)doSwitch(forLoop.getRange()); + loopElements.forEach(e -> { + this.symbols = new SymbolTable(currentSymbols); + this.symbols.bind(varName, e); + + execute(forLoop.getStatements()); + + this.symbols = currentSymbols; + }); + + return null; + } + + @Override + public Object caseCounterRange(final CounterRange range) { + final boolean reverse = range.getStart() > range.getEnd(); + final int min = Math.min(range.getStart(), range.getEnd()); + final int max = Math.max(range.getStart(), range.getEnd()); + final int count = max - min; + + Stream<Integer> values = IntStream.rangeClosed(0, count) + .boxed(); + + if(reverse) { + values = values.map(i -> max - i); + } else { + values = values.map(i -> min + i); + } + + return values; + } + + @Override + public Object casePipelineArray(final PipelineArray range) { + if(range.getReferences().isEmpty()) { + return new ArrayList<>(pipelineTable.values()).stream(); + } + + return range.getReferences() + .stream() + .map(this::doSwitch) + .filter(Objects::nonNull); + } + + @Override + public Object casePipelineDefinitionReference(final PipelineDefinitionReference reference) { + final String name = reference.getPipeline().getName(); + + return symbols.get(name); + } + + @Override + public Object caseVariableReference(final VariableReference reference) { + final String name = reference.getLoop().getName(); + + return symbols.get(name); + } + + @Override + public Object caseApplyStatement(final ApplyStatement stmt) { + final int count = stmt.getCount(); + final List<Pipeline> pipelines = + stmt.getPipelines() + .stream() + .map(this::doSwitch) + .filter(Objects::nonNull) + .filter(Pipeline.class::isInstance) + .map(Pipeline.class::cast) + .collect(Collectors.toUnmodifiableList()); + + String filename = stmt.getFile(); + + final Matcher matcher = varPattern.matcher(filename); + + final Set<String> vars = new HashSet<>(); + while(matcher.find()) { + vars.add(filename.substring(matcher.start() + 2, matcher.end() - 1)); + } + + for(final String var : vars) { + String replacement = null; + + Object value = symbols.get(var); + if(value instanceof Integer) { + replacement = value.toString(); + } else if(value instanceof Pipeline) { + replacement = ((Pipeline)value).getName(); + } + + if(replacement == null) { + log.warn("Unable to replace ${{}}", var); + continue; + } + + filename = filename.replace("${" + var + "}", replacement); + } + + log.info("Writing {}", filename); + + final PropertiesSpecification specification = PropertiesSpecification.builder().build(); + final Properties emptyProperties = new Properties(specification); + + Stream<Properties> stream = Stream.generate(() -> emptyProperties); + + for(final Pipeline pipe : pipelines) { + for(final GeneratorFunction function : pipe.getSteps()) { + stream = stream.map(function::apply); + } + } + + new File(filename).getParentFile().mkdirs(); + + try(final PropertiesWriter writer = new PropertiesWriter(new File(filename))) { + stream.limit(count) + .forEach(writer::addProperties); + } catch (final Exception e) { + log.error("Failed to write properties to file '{}'.", filename); + } + + return null; + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/SymbolTable.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/SymbolTable.java new file mode 100644 index 0000000000000000000000000000000000000000..3570303e78729ba0448f2b5518f500f631b3e405 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/internal/SymbolTable.java @@ -0,0 +1,60 @@ +package de.evoal.generator.main.internal; + +import java.util.HashMap; +import java.util.Map; + +public class SymbolTable { + /** + * If we have an outer scope. + */ + private final SymbolTable parent; + + /** + * The symbols + */ + private final Map<String, Object> symbols = new HashMap<>(); + + public SymbolTable(final SymbolTable parent) { + this.parent = parent; + } + + public SymbolTable(final SymbolTable parent, final Map<String, Object> symbols) { + this.parent = parent; + this.symbols.putAll(symbols); + } + + public void bind(final String name, final Object obj) { + symbols.put(name, obj); + } + + public Object get(final String name) { + if(symbols.containsKey(name)) { + return symbols.get(name); + } + + if(parent != null) { + return parent.get(name); + } + + return null; + } + + /** + * @return All visible (non-hidden variable bindings. + */ + public Map<String, Object> getVariables() { + Map<String, Object> variables = new HashMap<>(); + + collectVariables(variables); + + return variables; + } + + private void collectVariables(final Map<String, Object> entries) { + if(parent != null) { + parent.collectVariables(entries); + } + + entries.putAll(symbols); + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/utils/ELHelper.java b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/utils/ELHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..17aac76bcef8eb9e04fa0ffd5b31b1f54598b3e8 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/de/evoal/generator/main/utils/ELHelper.java @@ -0,0 +1,36 @@ +package de.evoal.generator.main.utils; + +import de.evoal.languages.model.el.DoubleLiteral; +import de.evoal.languages.model.instance.Array; +import de.evoal.languages.model.instance.Instance; +import de.evoal.languages.model.instance.LiteralValue; + +import java.util.List; +import java.util.stream.Collectors; + +public final class ELHelper { + private ELHelper() {} + + public record Distribution(double μ, double σ) {} + + public static double readDouble(final Instance instance, final String name) { + return ((DoubleLiteral)((LiteralValue)instance.findAttribute(name).getValue()).getLiteral()).getValue(); + } + + public static List<Distribution> readDistributions(final Instance instance, final String name) { + final Array array = (Array)instance.findAttribute(name).getValue(); + + return array.getValues() + .stream() + .map(Instance.class::cast) + .map(ELHelper::readDistribution) + .collect(Collectors.toUnmodifiableList()); + } + + private static Distribution readDistribution(final Instance instance) { + final double μ = ELHelper.readDouble(instance, "μ"); + final double σ = ELHelper.readDouble(instance, "σ"); + + return new Distribution(μ, σ); + } +} diff --git a/src/core/de.evoal.generator.main/src/main/java/module-info.java b/src/core/de.evoal.generator.main/src/main/java/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..0f7d69ea9124a12777f4e3a5142566ed5bfe4548 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/java/module-info.java @@ -0,0 +1,35 @@ +module de.evoal.generator.main { + requires java.base; + + requires lombok; + + requires org.slf4j; + + requires jakarta.inject.api; + requires jakarta.enterprise.cdi.api; + + requires org.eclipse.emf.common; + requires org.eclipse.emf.ecore; + requires org.eclipse.xtext; + + requires de.evoal.languages.model.ddl; + requires de.evoal.languages.model.dl; + requires de.evoal.languages.model.el; + requires de.evoal.languages.model.generator; + requires de.evoal.languages.model.instance; + + requires de.evoal.languages.model.ddl.dsl; + requires de.evoal.languages.model.dl.dsl; + requires de.evoal.languages.model.el.dsl; + requires de.evoal.languages.model.generator.dsl; + + requires de.evoal.core.api; + requires guice; + requires commons.math3; + + opens de.evoal.generator.main; + opens de.evoal.generator.main.benchmarks; + opens de.evoal.generator.main.cdi; + opens de.evoal.generator.main.functions; + opens de.evoal.generator.main.generators; +} diff --git a/src/core/de.evoal.generator.main/src/main/resources/META-INF/MANIFEST.MF b/src/core/de.evoal.generator.main/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000000000000000000000000000000..9d885be534121a9f146924f4832955dfe2ee2d4b --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1 @@ +Manifest-Version: 1.0 diff --git a/src/core/de.evoal.generator.main/src/main/resources/META-INF/beans.xml b/src/core/de.evoal.generator.main/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000000000000000000000000000000..848dca3b29cc3f1f9879d2c86d586612e5d8b3e7 --- /dev/null +++ b/src/core/de.evoal.generator.main/src/main/resources/META-INF/beans.xml @@ -0,0 +1,6 @@ +<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" + bean-discovery-mode="annotated" + version="2.0"> +</beans> \ No newline at end of file diff --git a/src/core/de.evoal.surrogate.api/pom.xml b/src/core/de.evoal.surrogate.api/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..42110b7233ce2b0a9c42b70cfe59c5521f6ed77e --- /dev/null +++ b/src/core/de.evoal.surrogate.api/pom.xml @@ -0,0 +1,137 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>de.evoal.core</groupId> + <artifactId>releng.parent</artifactId> + <version>0.9.0-SNAPSHOT</version> + + <relativePath>../de.evoal.core.releng.parent</relativePath> + </parent> + + <artifactId>surrogate.api</artifactId> + <name>EvoAl - Surrogate - API</name> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>core.api</artifactId> + <scope>provided</scope> + </dependency> + + <!-- DSL dependencies --> + <!-- Xtext --> + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.ecore</artifactId> + </dependency> + + <dependency> + <groupId>org.eclipse.emf</groupId> + <artifactId>org.eclipse.emf.common</artifactId> + </dependency> + + <dependency> + <groupId>org.eclipse.xtext</groupId> + <artifactId>org.eclipse.xtext</artifactId> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.ddl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.dl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.el</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.instance</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.mll</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.ddl.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.dl.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.el.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.instance.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + + <dependency> + <groupId>de.evoal.languages</groupId> + <artifactId>de.evoal.languages.model.mll.dsl</artifactId> + <version>${evoal.languages.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>copy-dependencies</goal> + </goals> + <configuration> + <outputDirectory>${project.build.directory}/${project.artifactId}-dependencies</outputDirectory> + <includeScope>runtime</includeScope> + <excludeScope>provided</excludeScope> + <excludeTransitive>true</excludeTransitive> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile> + <manifest> + <addClasspath>true</addClasspath> + <classpathPrefix>${project.artifactId}-dependencies/</classpathPrefix> + </manifest> + </archive> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/SurrogateBlackboardEntry.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/SurrogateBlackboardEntry.java new file mode 100644 index 0000000000000000000000000000000000000000..eddfbc21cca16a15cd8123e14b525e4047be088f --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/SurrogateBlackboardEntry.java @@ -0,0 +1,15 @@ +package de.evoal.surrogate.api; + +public final class SurrogateBlackboardEntry { + /** + * Loaded generator configuration. + */ + public static final String SURROGATE_CONFIGURATION = "surrogate:configuration"; + + /** + * Configuration file for the data generator. + */ + public static final String SURROGATE_CONFIGURATION_FILE = "surrogate:configuration-file"; + + private SurrogateBlackboardEntry() {} +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/SurrogateInformationCalculator.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/SurrogateInformationCalculator.java new file mode 100644 index 0000000000000000000000000000000000000000..fdf52f48423995ea0388db5ebd5c7bda034fcf6c --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/SurrogateInformationCalculator.java @@ -0,0 +1,28 @@ +package de.evoal.surrogate.api; + +import de.evoal.core.api.properties.stream.PropertiesStreamSupplier; +import de.evoal.languages.model.mll.DefinedFunctionName; +import de.evoal.surrogate.api.function.SurrogateFunction; +import de.evoal.surrogate.api.configuration.SurrogateConfiguration; + +import java.util.List; + +/** + * Calculates information on the calculated surrogate, such as cross validation + * values, goodness of fit, and so on. + */ +public interface SurrogateInformationCalculator { + /** + * Configures the calculator with the given parameters. + * + * @param function + * @param config + * @param parameters + */ + public void configure(final SurrogateFunction function, final SurrogateConfiguration config, final List<Object> parameters, final PropertiesStreamSupplier trainingData); + + /** + * Executes the calculation. + */ + public void execute(); +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/FunctionCombinerConfiguration.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/FunctionCombinerConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..ca98326d8a4ff4a650cb15d308c5e5534a1f4005 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/FunctionCombinerConfiguration.java @@ -0,0 +1,86 @@ +package de.evoal.surrogate.api.configuration; + +import de.evoal.languages.model.ddl.DataDescription; +import de.evoal.languages.model.mll.PartialSurrogateFunctionDefinition; +import de.evoal.languages.model.mll.SurrogateLayerDefinition; +import de.evoal.surrogate.api.function.FunctionCombiner; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Configuration of a {@link FunctionCombiner}. + */ +@Data +public class FunctionCombinerConfiguration { + /** + * List of functions within this mapping. + */ + private List<PartialFunctionConfiguration> functions = new ArrayList<>(); + + /** + * Configuration of required properties + */ + private final List<String> inputDimensions = new ArrayList<>(); + + /** + * Name of the mapping. + */ + private String name; + + /** + * Configuration of generated properties + */ + private final List<String> outputDimensions = new ArrayList<>(); + + public static FunctionCombinerConfiguration from(final SurrogateLayerDefinition definition) { + final FunctionCombinerConfiguration configuration = new FunctionCombinerConfiguration(); + configuration.setName(definition.getName()); + + final List<String> inputs = + definition.getFunctions() + .stream() + .map(PartialSurrogateFunctionDefinition::getInputs) + .flatMap(l -> l.stream().map(DataDescription::getName)) + .distinct() + .collect(Collectors.toList()); + + final List<String> outputs = + definition.getFunctions() + .stream() + .map(PartialSurrogateFunctionDefinition::getOutputs) + .flatMap(l -> l.stream().map(DataDescription::getName)) + .distinct() + .collect(Collectors.toList()); + + configuration.getInputDimensions().addAll(inputs); + configuration.getOutputDimensions().addAll(outputs); + + definition.getFunctions() + .stream() + .map(PartialFunctionConfiguration::from) + .forEach(configuration.functions::add); + + return configuration; + } + + public static FunctionCombinerConfiguration from(final FunctionCombinerConfiguration config) { + final FunctionCombinerConfiguration configuration = new FunctionCombinerConfiguration(); + configuration.setName(config.getName()); + + configuration.getInputDimensions() + .addAll(config.getInputDimensions()); + configuration.getOutputDimensions() + .addAll(config.getOutputDimensions()); + + config.getFunctions() + .stream() + .map(PartialFunctionConfiguration::from) + .forEach(configuration.functions::add); + + return configuration; + } +} + diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/Parameter.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/Parameter.java new file mode 100644 index 0000000000000000000000000000000000000000..fb49269b268f858217f1f2907d2164f6b8723a0e --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/Parameter.java @@ -0,0 +1,41 @@ +package de.evoal.surrogate.api.configuration; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import de.evoal.languages.model.instance.LiteralValue; +import de.evoal.languages.model.instance.Name; +import de.evoal.surrogate.main.jackson.ReflectiveDeserializer; +import de.evoal.surrogate.main.jackson.ReflectiveSerializer; +import de.evoal.languages.model.instance.Attribute; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@Builder +@Data +@NoArgsConstructor +public class Parameter { + private String name; + + @JsonDeserialize(using = ReflectiveDeserializer.class) + @JsonSerialize(using = ReflectiveSerializer.class) + private Object value; + + public static Parameter from(final Attribute attribute) { + final Parameter parameter = new Parameter(); + parameter.setName(((Name)attribute.getName()).getName().getName()); + parameter.setValue(((LiteralValue)attribute.getValue()).getLiteral().getValue()); + + return parameter; + } + + public static Parameter from(final Parameter config) { + final Parameter parameter = new Parameter(); + parameter.setName(config.getName()); + parameter.setValue(config.getValue()); + + return parameter; + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/PartialFunctionConfiguration.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/PartialFunctionConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..6ebc0c1e5f37a9d828fa34fdcfe6e690cea2a67d --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/PartialFunctionConfiguration.java @@ -0,0 +1,105 @@ +package de.evoal.surrogate.api.configuration; + +import de.evoal.languages.model.ddl.DataDescription; +import de.evoal.languages.model.mll.PartialSurrogateFunctionDefinition; +import de.evoal.surrogate.api.function.PartialSurrogateFunction; +import lombok.Data; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Configuration of a {@link PartialSurrogateFunction}. + */ +@Data +public class PartialFunctionConfiguration { + /** + * List of required input dimensions + */ + private final List<String> inputDimensions = new ArrayList<>(); + + /** + * Name of the function to use. + + */ + private String name; + + /** + * List of calculated properties. + */ + private final List<String> outputDimensions = new ArrayList<>(); + + /** + * Function-specific configuration parameters. + */ + private final List<Parameter> parameters = new ArrayList<>(); + + /** + * The precalculated state of the function. + */ + private final List<Parameter> state = new ArrayList<>(); + + /** + * Parameters of outputs + */ + private final Map<String, List<Parameter>> outputParameters = new HashMap<>(); + + public void addOutputParameter(final String name, final Parameter parameter) { + List<Parameter> content = outputParameters.get(name); + + if(content == null) { + content = new ArrayList<>(); + content.add(parameter); + outputParameters.put(name, content); + } else { + content.add(parameter); + } + } + + public static PartialFunctionConfiguration from(final PartialSurrogateFunctionDefinition definition) { + final PartialFunctionConfiguration configuration = new PartialFunctionConfiguration(); + configuration.setName(definition.getName().getName()); + + final List<String> inputs = + definition.getInputs() + .stream() + .map(DataDescription::getName) + .collect(Collectors.toList()); + + final List<String> outputs = + definition.getOutputs() + .stream() + .map(DataDescription::getName) + .collect(Collectors.toList()); + + configuration.getInputDimensions().addAll(inputs); + configuration.getOutputDimensions().addAll(outputs); + + definition.getParameters() + .stream() + .map(Parameter::from) + .forEach(p -> configuration.getParameters().add(p)); + + return configuration; + } + + public static PartialFunctionConfiguration from(final PartialFunctionConfiguration config) { + final PartialFunctionConfiguration configuration = new PartialFunctionConfiguration(); + configuration.setName(config.getName()); + + configuration.getInputDimensions() + .addAll(config.getInputDimensions()); + configuration.getOutputDimensions() + .addAll(config.getOutputDimensions()); + + config.getParameters() + .stream() + .map(Parameter::from) + .forEach(p -> configuration.getParameters().add(p)); + + return configuration; + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/SurrogateConfiguration.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/SurrogateConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..2224b0f31ab15f51164b958f9f0a643456254b84 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/configuration/SurrogateConfiguration.java @@ -0,0 +1,60 @@ +package de.evoal.surrogate.api.configuration; + +import de.evoal.languages.model.mll.SurrogateDefinition; +import de.evoal.surrogate.api.function.SurrogateFunction; +import lombok.Data; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Configuration of a {@link SurrogateFunction}. + */ +@Data +public class SurrogateConfiguration { + /** + * List of mappings. At least one. + */ + private final List<FunctionCombinerConfiguration> mappings = new ArrayList<>(); + + /** + * Parameters of outputs + */ + private final Map<String, List<Parameter>> outputParameters = new HashMap<>(); + + public void addOutputParameter(final String name, final Parameter parameter) { + List<Parameter> content = outputParameters.get(name); + + if(content == null) { + content = new ArrayList<>(); + content.add(parameter); + outputParameters.put(name, content); + } else { + content.add(parameter); + } + } + + public static SurrogateConfiguration from(final SurrogateDefinition definition) { + final SurrogateConfiguration configuration = new SurrogateConfiguration(); + + definition.getLayers() + .stream() + .map(FunctionCombinerConfiguration::from) + .forEach(configuration.mappings::add); + + return configuration; + } + + public static SurrogateConfiguration from(final SurrogateConfiguration original) { + final SurrogateConfiguration configuration = new SurrogateConfiguration(); + + original.getMappings() + .stream() + .map(FunctionCombinerConfiguration::from) + .forEach(configuration.mappings::add); + + return configuration; + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/AbstractPartialSurrogateFunction.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/AbstractPartialSurrogateFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..53067ddee58a8f3bce764dd1bed25a69208e6b2f --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/AbstractPartialSurrogateFunction.java @@ -0,0 +1,52 @@ +package de.evoal.surrogate.api.function; + +import java.util.List; + +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.core.api.properties.PropertiesSpecification; + +/** + * Base class for regression functions storing the input and output properties. + */ +public abstract class AbstractPartialSurrogateFunction implements PartialSurrogateFunction { + protected static void addParameter(final String name, final Object value, final List<Parameter> parameters) { + final Parameter parameter = Parameter.builder() + .name(name) + .value(value) + .build(); + + parameters.add(parameter); + } + + private final PropertiesSpecification input; + private final PropertiesSpecification output; + private final PartialFunctionConfiguration configuration; + private final List<Parameter> parameters; + + public AbstractPartialSurrogateFunction(final PartialFunctionConfiguration configuration, final List<Parameter> functionParameters, final PropertiesSpecification input, final PropertiesSpecification output) { + this.configuration = configuration; + this.parameters = functionParameters; + this.input = input; + this.output = output; + } + + @Override + public PropertiesSpecification getUsedProperties() { + return input; + } + + @Override + public PropertiesSpecification getOutputProperty() { + return output; + } + + @Override + public List<Parameter> getParameters() { + return parameters; + } + + public PartialFunctionConfiguration getConfiguration() { + return configuration; + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/AbstractPartialSurrogateFunctionFactory.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/AbstractPartialSurrogateFunctionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..aac2b1254fc893e1c83f7811b315dcf002e41795 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/AbstractPartialSurrogateFunctionFactory.java @@ -0,0 +1,36 @@ +package de.evoal.surrogate.api.function; + +import java.util.List; + +import de.evoal.core.api.properties.stream.PropertiesBasedPropertiesPairStreamSupplier; +import de.evoal.core.api.properties.stream.PropertiesPairStreamSupplier; +import de.evoal.core.api.properties.stream.PropertiesStreamSupplier; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.core.api.properties.PropertiesSpecification; +import lombok.extern.slf4j.Slf4j; + +/** + * Base class for regression factories. + */ +@Slf4j +public abstract class AbstractPartialSurrogateFunctionFactory implements PartialSurrogateFunctionFactory { + protected abstract PartialSurrogateFunction calculateRegression(final PartialFunctionConfiguration configuration, final List<Parameter> parameters, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, PropertiesSpecification producedOutput, final PropertiesPairStreamSupplier provider); + + @Override + public final PartialSurrogateFunction create(final PartialFunctionConfiguration configuration, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput, final PropertiesStreamSupplier training) { + if(configuration.getState().isEmpty()) { + final PropertiesPairStreamSupplier provider = new PropertiesBasedPropertiesPairStreamSupplier(training, requiredInput, producedOutput); + final PartialSurrogateFunction function = calculateRegression(configuration, configuration.getParameters(), actualInput, requiredInput, producedOutput, provider); + + configuration.getState() + .addAll(function.getParameters()); + + return function; + } else { + return restoreRegression(configuration, actualInput, requiredInput, producedOutput); + } + } + + protected abstract PartialSurrogateFunction restoreRegression(final PartialFunctionConfiguration configuration, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput); +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/FunctionCombiner.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/FunctionCombiner.java new file mode 100644 index 0000000000000000000000000000000000000000..92bf820abd42cffa947fd392371d9fd3b9dbc42e --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/FunctionCombiner.java @@ -0,0 +1,81 @@ +package de.evoal.surrogate.api.function; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class takes several {@link PartialSurrogateFunction} and combines them to a + * single mapping function. Each partial surrogate function may take a subset of the + * input properties and generate a subset of the output properties. + */ +public class FunctionCombiner implements MappingFunction { + /** + * Specification of the input vector. + */ + private final PropertiesSpecification inputSpecification; + + /** + * Specification of the output vector. + */ + private final PropertiesSpecification outputSpecification; + + /** + * All regression functions for calculating the output. + */ + private final List<PartialSurrogateFunction> functions; + + /** + * A map storing the indices of all property specifications in the output properties. + */ + private final Map<PropertySpecification, Integer> indices = new HashMap<>(); + + public FunctionCombiner(final List<PartialSurrogateFunction> functions, final PropertiesSpecification input, final PropertiesSpecification output) { + this.inputSpecification = input; + this.outputSpecification = output; + + for(final PartialSurrogateFunction function : functions) { + for(final PropertySpecification specification : function.getOutputProperty().getProperties()) { + indices.put(specification, outputSpecification.indexOf(specification)); + } + } + + this.functions = functions; + } + + @Override + public Properties apply(final Properties input) { + final Properties output = new Properties(outputSpecification); + + for(final PartialSurrogateFunction entry : functions) { + final double [] values = entry.apply(input); + + for(int index = 0; index < values.length; ++index) { + output.set(indices.get(entry.getOutputProperty().getProperties().get(index)), values[index]); + } + } + + return output; + } + + @Override + public PropertiesSpecification getInputSpecification() { + return inputSpecification; + } + + /** + * This function gives access to + */ + public PartialSurrogateFunction[] getFunctions() { + return functions.toArray(new PartialSurrogateFunction[functions.size()]); + } + + @Override + public PropertiesSpecification getOutputSpecification() { + return outputSpecification; + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/MappingFunction.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/MappingFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..f45fd19de1933b7e58f587b0b86edff4981b7b74 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/MappingFunction.java @@ -0,0 +1,26 @@ +package de.evoal.surrogate.api.function; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; + +/** + * General interface of a functions. It receives some input properties that + * adhere to the input properties specification and calculates some + * properties based on the input that adheres to the output properties specification. + */ +public interface MappingFunction { + /** + * Applies the mapping function. + */ + public Properties apply(final Properties input); + + /** + * @return An empty input vector with all property headers set. + */ + public PropertiesSpecification getInputSpecification(); + + /** + * @return An empty output vector with all property headers set. + */ + public PropertiesSpecification getOutputSpecification(); +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/PartialSurrogateFunction.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/PartialSurrogateFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..b94e0de6c7922ff0628698719ff6c441dbbb6ef1 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/PartialSurrogateFunction.java @@ -0,0 +1,44 @@ +package de.evoal.surrogate.api.function; + +import java.util.Collection; + +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; + +/** + * A partial surrogate function calculates only specific properties. Several + * parallel or chained partial surrogate function make up the complete surrogate + * function. + */ +public interface PartialSurrogateFunction { + /** + * Applies the regression to {@code input} and returns the calculated + * value. + * + * @param input The input properties for the regression. + * @return The calculated value. + */ + public double [] apply(final Properties input); + + /** + * @return The generated property. + */ + public PropertiesSpecification getOutputProperty(); + + /** + * @return A collection of parameters to restore the regression function. + */ + public Collection<? extends Parameter> getParameters(); + + /** + * @return An empty properties vector containing all consumed properties. + */ + public PropertiesSpecification getUsedProperties(); + + /** + * @return The configuration object. + */ + public PartialFunctionConfiguration getConfiguration(); +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/PartialSurrogateFunctionFactory.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/PartialSurrogateFunctionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..132895b4b4f0763068b79a0a17627b1d8c3acb83 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/PartialSurrogateFunctionFactory.java @@ -0,0 +1,28 @@ +package de.evoal.surrogate.api.function; + +import de.evoal.core.api.properties.stream.PropertiesStreamSupplier; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; + +import java.util.stream.Stream; + +/** + * Factory for creating regression functions. + */ +public interface PartialSurrogateFunctionFactory { + /** + * Method for creating a regression function. + * + * @param configuration Regression configuration. + * @param actualInput The input properties vector that will be passed to the + * regression function when it is applied + * {@link PartialSurrogateFunction#apply(Properties)}. + * @param requiredInput The properties that the regression function has to consume. + * @param producedOutput The property that the regression function has to produce. + * @param training Training data with target values. + * + * @return The resulting regression function. + */ + public PartialSurrogateFunction create(final PartialFunctionConfiguration configuration, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput, final PropertiesStreamSupplier training); +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/SurrogateFunction.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/SurrogateFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..13233e1bd622a0211a36e6aa2e4cec61036e2421 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/SurrogateFunction.java @@ -0,0 +1,60 @@ +package de.evoal.surrogate.api.function; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import lombok.Data; +import lombok.NonNull; + +import java.util.List; + +/** + * A surrogate function replaces the actual function used in the optimization + * if the actual function is too expensive to calculate or even unknown. A + * surrogate function transforms input properties into output properties + * according to a known oder learned function. + * + * A surrogate function may consist of several chained combined functions (c.f., + * {@link FunctionCombiner}. The surrogate function then takes the input and + * calculates an output' using a combined function. The output' is then used + * as input for the next combined function in the chain. The output of the last + * combined function is the result of the surrogate function. + */ +@Data +public final class SurrogateFunction implements MappingFunction { + /** + * The chain of property mappings. + */ + private final List<FunctionCombiner> mappings; + + /** + * Property specification of the input of the complete surrogate function. + */ + private final PropertiesSpecification inputSpecification; + + /** + * Property specification of the output of the complete surrogate function. + */ + private final PropertiesSpecification outputSpecification; + + /** + * Creates a new surrogate function for the combined functions. + * + * @param mappings The chain of combined functions. + */ + public SurrogateFunction(final @NonNull List<FunctionCombiner> mappings) { + this.mappings = mappings; + this.inputSpecification = mappings.get(0).getInputSpecification(); + this.outputSpecification = mappings.get(mappings.size() - 1).getOutputSpecification(); + } + + @Override + public Properties apply(final Properties input) { + Properties current = input; + + for(final MappingFunction function : mappings) { + current = function.apply(current); + } + + return current; + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/package-info.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..e5482784f0f48a9a6ada8584c2ce472deac94164 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/function/package-info.java @@ -0,0 +1 @@ +package de.evoal.surrogate.api.function; \ No newline at end of file diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/training/TrainingDataManager.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/training/TrainingDataManager.java new file mode 100644 index 0000000000000000000000000000000000000000..1e8507b3fbb2352be8a24897d3aeac2f8dced994 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/api/training/TrainingDataManager.java @@ -0,0 +1,16 @@ +package de.evoal.surrogate.api.training; + +import de.evoal.core.api.properties.stream.FileBasedPropertiesStreamSupplier; +import de.evoal.core.api.properties.stream.PropertiesStreamSupplier; +import lombok.Getter; +import lombok.Setter; + +import javax.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class TrainingDataManager { + + @Getter + @Setter + private PropertiesStreamSupplier trainingStream; +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/SurrogateMain.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/SurrogateMain.java new file mode 100644 index 0000000000000000000000000000000000000000..396244943d507e06a56ef6c9a90717f221f0eb4e --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/SurrogateMain.java @@ -0,0 +1,47 @@ +package de.evoal.surrogate.main; + +import de.evoal.core.api.cdi.BeanFactory; +import de.evoal.core.api.cdi.BlackboardValue; +import de.evoal.core.api.cdi.MainClass; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.inject.Named; + +import de.evoal.languages.model.mll.MachineLearningConfiguration; +import de.evoal.surrogate.api.SurrogateBlackboardEntry; +import de.evoal.surrogate.main.internal.StatementExecutor; +import de.evoal.surrogate.main.internal.SymbolTable; +import lombok.extern.slf4j.Slf4j; + +/** + * Program for pre-calculating regressions to minimize runtime overhead of + * recalculating them. + */ +@Slf4j +@Named("surrogate-training") +@ApplicationScoped +public class SurrogateMain implements MainClass { + + @Inject + @BlackboardValue(SurrogateBlackboardEntry.SURROGATE_CONFIGURATION) + private MachineLearningConfiguration mlConfiguration; + + @Override + public void run() { + log.info("Training surrogate models and measuring GOF values."); + + final SymbolTable globalTable = new SymbolTable(null); + mlConfiguration.getDefinitions() + .stream() + .forEach(def -> globalTable.put(def.getName(), def)); + + final StatementExecutor executor = BeanFactory.create(StatementExecutor.class); + executor.setSymbolTable(globalTable); + + mlConfiguration.getStatements() + .forEach(executor::evaluate); + + log.info("Finished surrogate model training."); + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/cdi/IOProducer.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/cdi/IOProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..bc1b51d72d06c3e9115f5682a6cda5fd1e735a81 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/cdi/IOProducer.java @@ -0,0 +1,56 @@ +package de.evoal.surrogate.main.cdi; + +import java.io.*; +import java.util.function.BiConsumer; +import java.util.function.Function; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Produces; +import javax.inject.Named; + +import com.fasterxml.jackson.databind.ObjectMapper; +import de.evoal.surrogate.api.configuration.SurrogateConfiguration; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +/** + * Loader for different files necessary to train a prediction and load/store + * its state. + */ +@ApplicationScoped +@Slf4j +public class IOProducer { + + @Produces + @Named("surrogate-loader") + public Function<@NonNull File, @NonNull SurrogateConfiguration> createSurrogateLoader() { + return (file) -> loadPredictiveConfiguration(file); + } + + @Produces + @Named("surrogate-writer") + public BiConsumer<@NonNull SurrogateConfiguration, @NonNull File> createSurrogateWriter() { + return (config, file) -> storePredictiveConfiguration(config, file); + } + + private static SurrogateConfiguration loadPredictiveConfiguration(final File file) { + log.info("Loading predictive configuration from {}.", file); + try(final InputStream is = new FileInputStream(file)) { + return new ObjectMapper().readValue(is, SurrogateConfiguration.class); + } catch (final IOException e) { + log.error("Failed to load predictive configuration from {}.", file, e); + + throw new IllegalStateException("Failed to load predictive configuration from " + file); + } + } + + private static void storePredictiveConfiguration(final SurrogateConfiguration config, final File file) { + log.info("Storing predictive configuration to {}.", file); + + try { + new ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(file, config); + } catch (IOException e) { + log.error("Failed to store configuration to {}", file, e); + } + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/cdi/MLLConfigurationProducer.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/cdi/MLLConfigurationProducer.java new file mode 100644 index 0000000000000000000000000000000000000000..a17d773fd40e4b0abda5f09579efd44fc674abe2 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/cdi/MLLConfigurationProducer.java @@ -0,0 +1,126 @@ +package de.evoal.surrogate.main.cdi; + +import com.google.inject.Injector; +import de.evoal.core.api.board.Blackboard; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.BlackboardValue; +import de.evoal.languages.model.ddl.dsl.DataDescriptionLanguageStandaloneSetup; +import de.evoal.languages.model.ddl.impl.DdlPackageImpl; +import de.evoal.languages.model.dl.dsl.DefinitionLanguageStandaloneSetup; +import de.evoal.languages.model.dl.impl.DlPackageImpl; +import de.evoal.languages.model.el.dsl.ExpressionLanguageStandaloneSetup; +import de.evoal.languages.model.el.impl.ELPackageImpl; +import de.evoal.languages.model.mll.dsl.MachineLearningLanguageStandaloneSetup; +import de.evoal.languages.model.mll.MachineLearningConfiguration; +import de.evoal.languages.model.mll.impl.MllPackageImpl; +import de.evoal.surrogate.api.SurrogateBlackboardEntry; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.resource.XtextResourceSet; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Dependent; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; +import java.io.File; +import java.util.Optional; + +@ApplicationScoped +@Slf4j +public class MLLConfigurationProducer { + public void loadModel(final @Observes BlackboardEntry value, final Blackboard board) { + if(!SurrogateBlackboardEntry.SURROGATE_CONFIGURATION_FILE.equals(value.getLabel())) { + return; + } + + final String configurationFile = board.get(value.getLabel()); + + log.info("Using surrogate configuration from '{}'.", configurationFile); + + initializeEMF(); + + final File file = new File(configurationFile); + + if(!file.isFile()) { + log.info("Configured surrogate configuration is a folder."); + throw new IllegalArgumentException("Please specify a surrogate file."); + } + + if(!file.canRead()) { + log.info("Configured surrogate configuration cannot be read."); + throw new IllegalArgumentException("Please specify a readable surrogate file."); + } + + final MachineLearningConfiguration configuration = read(file).get(); + board.bind(SurrogateBlackboardEntry.SURROGATE_CONFIGURATION, configuration); + } + + /** + * Initialize the model packages and perform the parser setup. + */ + private void initializeEMF() { + DdlPackageImpl.init(); + ELPackageImpl.init(); + DlPackageImpl.init(); + MllPackageImpl.init(); + + DataDescriptionLanguageStandaloneSetup.doSetup(); + ExpressionLanguageStandaloneSetup.doSetup(); + DefinitionLanguageStandaloneSetup.doSetup(); + MachineLearningLanguageStandaloneSetup.doSetup(); + } + + /** + * Parses the given generator file and returns the corresponding model. + * + * @param modelFile The model file to read. + * @return The data validation model or an empty optional. + */ + private Optional<MachineLearningConfiguration> read(final File modelFile) { + log.info("Reading model file {}.", modelFile); + + final Injector injector = new MachineLearningLanguageStandaloneSetup().createInjectorAndDoEMFRegistration(); + final XtextResourceSet resourceSet = injector.getInstance(XtextResourceSet.class); + resourceSet.addLoadOption(XtextResource.OPTION_RESOLVE_ALL, Boolean.TRUE); + resourceSet.addLoadOption(XtextResource.OPTION_ENCODING, "UTF-8"); + + try { + final URI modelURI = URI.createFileURI(modelFile.getAbsolutePath()); + final Resource resource = resourceSet.getResource(modelURI, true); + resource.load(resourceSet.getLoadOptions()); + + if(!resource.getErrors().isEmpty()) { + for(Resource.Diagnostic diagnostic : resource.getErrors()) { + log.error("Error while processing rule '{}': {}", modelFile, diagnostic); + } + } + + if(!resource.getWarnings().isEmpty()) { + for(Resource.Diagnostic diagnostic : resource.getWarnings()) { + log.error("Warning while processing rule '{}': {}", modelFile, diagnostic); + } + } + + return Optional.of((MachineLearningConfiguration) resource.getContents().get(0)); + } catch (final Exception e) { + log.error("Unable to to generator file '{}'.", modelFile, e); + return Optional.empty(); + } + } + + @Produces @Dependent + @BlackboardValue(SurrogateBlackboardEntry.SURROGATE_CONFIGURATION) + public MachineLearningConfiguration injectMachineLearningConfiguration(final InjectionPoint ip, final Blackboard board) { + final BlackboardValue value = ip.getAnnotated().getAnnotation(BlackboardValue.class); + final Object result = board.get(value.value()); + + if(result instanceof MachineLearningConfiguration) { + return (MachineLearningConfiguration)result; + } + + throw new IllegalArgumentException("Unable to handle type " + result.getClass()); + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/cross/CrossValidationCalculator.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/cross/CrossValidationCalculator.java new file mode 100644 index 0000000000000000000000000000000000000000..32d660a553363b59496352831e2596dad2be388f --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/cross/CrossValidationCalculator.java @@ -0,0 +1,197 @@ +package de.evoal.surrogate.main.gof.cross; + +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.properties.stream.PropertiesStreamSupplier; +import de.evoal.core.api.utils.Requirements; +import de.evoal.surrogate.api.SurrogateInformationCalculator; +import de.evoal.surrogate.api.configuration.FunctionCombinerConfiguration; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.configuration.SurrogateConfiguration; +import de.evoal.surrogate.api.function.PartialSurrogateFunction; +import de.evoal.surrogate.api.function.SurrogateFunction; +import de.evoal.surrogate.main.internal.SurrogateFactory; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.math3.stat.descriptive.moment.Mean; +import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; +import java.util.Arrays; +import java.util.List; + +/** + * Calculates cross validation values. + */ +@Dependent +@Named("cross-validation") +@Slf4j +public class CrossValidationCalculator implements SurrogateInformationCalculator { + + /** + * The surrogate function to use. + */ + private SurrogateFunction function; + + /** + * The number of groups for the validation. + */ + private int k = 1; + + /** + * The surrogate configuration for adding the calculated values + */ + private SurrogateConfiguration originalConfiguration; + + /** + * The training data used + */ + private CrossValidationPropertiesStreamSupplier trainingSupplier; + + @Override + public void execute() { + log.info("calculating of {}-fold cross validation of surrogate function :", k); + + for(int i = 0; i < originalConfiguration.getMappings().size(); ++i) { + final SurrogateConfiguration trainingConfiguration = SurrogateConfiguration.from(originalConfiguration); + + final FunctionCombinerConfiguration originalMapping = originalConfiguration.getMappings().get(i); + final FunctionCombinerConfiguration trainingMapping = trainingConfiguration.getMappings().get(i); + + validateMapping(originalMapping, trainingMapping); + } + } + + private void validateMapping(final FunctionCombinerConfiguration originalMapping, final FunctionCombinerConfiguration trainingMapping) { + log.info("Calculating cross validation for mapping {}.", originalMapping.getName()); + final int rowLength = originalMapping.getOutputDimensions().size() + 2; + + //final AsciiTable table = new AsciiTable(); + //table.addRule(); + //final Object [] cvLabels = originalMapping.getOutputDimensions().toArray(); + //final Object [] header = new String [rowLength]; + //header[0] = ""; + //header[rowLength - 1] = "Summe"; + //System.arraycopy(cvLabels, 0, header, 1, cvLabels.length); + //table.addRow(header); + //table.addRule(); + + // detail list for all input target + double [][] data = new double[rowLength - 1][k]; + + final PropertiesSpecification mappingInput = PropertiesSpecification.builder() + .add(originalMapping.getInputDimensions().stream()) + .build(); + final PropertiesSpecification mappingOutput = PropertiesSpecification.builder() + .add(originalMapping.getOutputDimensions().stream()) + .build(); + + final PropertiesSpecification [] fInputs = new PropertiesSpecification [originalMapping.getFunctions().size()]; + final PropertiesSpecification [] fOutputs = new PropertiesSpecification[originalMapping.getFunctions().size()]; + + for(int partition = 1; partition <= k; ++partition) { + trainingSupplier.setPartition(partition); + + //final Object[] tableData = new Object[rowLength]; + //tableData[0] = "Partition " + partition; + + for(int func = 0; func < originalMapping.getFunctions().size(); ++func) { + final PartialFunctionConfiguration fConfig = PartialFunctionConfiguration.from(trainingMapping.getFunctions() + .get(func)); + + final PropertiesSpecification fInput = PropertiesSpecification.builder() + .add(fConfig.getInputDimensions().stream()) + .build(); + final PropertiesSpecification fOutput = PropertiesSpecification.builder() + .add(fConfig.getOutputDimensions().stream()) + .build(); + + + final PartialSurrogateFunction mapping = SurrogateFactory.create(fConfig, fInput, fOutput, trainingSupplier); + + // sum of prediction errors in this partition for each output property + final double[] correctness = new double[fOutput.size()]; + trainingSupplier.getValidationStream() + .get() + .forEach(source -> { + final Properties predicted = new Properties(fOutput, mapping.apply(source)); + final Properties calculated = Properties.create(fOutput, source); + + for (int cvi = 0; cvi < calculated.size(); ++cvi) { + //correctness[cvi] = correctness[cvi] + Math.abs((calculated.get(cvi) - predicted.get(cvi)) / calculated.get(cvi)); + correctness[cvi] = correctness[cvi] + Math.abs(calculated.get(cvi) - predicted.get(cvi)); + } + }); + + // relate cumulative errors to partition size (number of test candidates) + for (int i = 0; i < correctness.length; ++i) { + correctness[i] = correctness[i] / trainingSupplier.getValidationStream().get().count(); + } + + for (int i = 0; i < correctness.length; ++i) { + // copy cumulative error to output table + //tableData[1 + mappingOutput.indexOf(fOutput.getProperties().get(i))] = String.format("%.4f", correctness[i]); + // copy cumulative error to data table + data[mappingOutput.indexOf(fOutput.getProperties().get(i))][partition - 1] = correctness[i]; + } + } + + // calculate sum of errors for partition + final int pi = partition; + data[data.length - 1][partition - 1] = Arrays.stream(data).mapToDouble(a -> a[pi - 1]).sum(); + //data[correctness.length][partition] = Arrays.stream(correctness).sum(); + //tableData[data.length] = String.format("%.4f", data[data.length - 1][partition]); + + //table.addRow(tableData); + } + + //table.addRule(); + + // calculate last row + // Object [] tableData = new Object[rowLength]; + //tableData[0] = "Ø / s"; + for(int i = 0; i < data.length; ++i) { + double avg = new Mean().evaluate(data[i]); + double sd = new StandardDeviation().evaluate(data[i]); + + //tableData[i+1] = String.format("%.4f / %.4f", avg, sd); + + if(i != data.length - 1) { + attach(originalMapping, + mappingOutput.getProperties().get(i), + new CrossValidationData(k, avg, k, sd)); + } + } + //table.addRow(tableData); + //table.addRule(); + //System.out.println(table.render(140)); + } + + private void attach(final FunctionCombinerConfiguration config, final PropertySpecification propertySpecification, final CrossValidationData data) { + for(final PartialFunctionConfiguration fConfig : config.getFunctions()) { + if(fConfig.getOutputDimensions().contains(propertySpecification.name())) { + data.attachTo(fConfig, propertySpecification.name()); + break; + } + } + } + + @Override + public String toString() { + return "cross validation"; + } + + @Override + public void configure(final SurrogateFunction function, final SurrogateConfiguration config, final List<Object> parameters, final PropertiesStreamSupplier trainingData) { + Requirements.requireSize(parameters, 1); + Requirements.requireType(parameters, 0, Integer.class); + Requirements.requireNotNull(function); + Requirements.requireNotNull(config); + + k = (Integer)parameters.get(0); + this.function = function; + this.originalConfiguration = config; + trainingSupplier = new CrossValidationPropertiesStreamSupplier(trainingData, k); + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/cross/CrossValidationData.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/cross/CrossValidationData.java new file mode 100644 index 0000000000000000000000000000000000000000..ee120c7dffac2a11836144ab1abacc4b32fb2132 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/cross/CrossValidationData.java @@ -0,0 +1,42 @@ +package de.evoal.surrogate.main.gof.cross; + +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; + +public class CrossValidationData { + private final int k; + private final double mean; + private final int partitionSize; + private final double sd; + + public CrossValidationData(final int k, final double mean, final int partitionSize, final double sd) { + this.k = k; + this.mean = mean; + this.partitionSize = partitionSize; + this.sd = sd; + } + + public double getMean() { + return mean; + } + + public double getSd() { + return sd; + } + + public void attachTo(final PartialFunctionConfiguration regression, final String outputName) { + attach(regression, outputName, "cross-validation-k", k); + attach(regression, outputName, "cross-validation-mean", mean); + attach(regression, outputName, "cross-validation-partition-size", partitionSize); + attach(regression, outputName, "cross-validation-sd", sd); + } + + private void attach(final PartialFunctionConfiguration regression, final String output, final String name, final Object value) { + final Parameter parameter = Parameter.builder() + .name(name) + .value(value) + .build(); + + regression.addOutputParameter(output, parameter); + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/cross/CrossValidationPropertiesStreamSupplier.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/cross/CrossValidationPropertiesStreamSupplier.java new file mode 100644 index 0000000000000000000000000000000000000000..8f9f049139efb3075999c1e2c1ce325d9d17148b --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/cross/CrossValidationPropertiesStreamSupplier.java @@ -0,0 +1,102 @@ +package de.evoal.surrogate.main.gof.cross; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.stream.PropertiesStreamSupplier; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; + +/** + * A stream supplier that leaves out a part of the training data. + */ +@Slf4j +public class CrossValidationPropertiesStreamSupplier implements PropertiesStreamSupplier { + + /** + * Numbers of partitions to generate. + */ + private final int k; + + /** + * Partition to leave out. + */ + @Setter + private int partition; + + /** + * The training data to split. + */ + private final PropertiesStreamSupplier trainingData; + + private final long traingDataCount; + + CrossValidationPropertiesStreamSupplier(final PropertiesStreamSupplier trainingData, final int k) { + this.trainingData = trainingData; + this.k = k; + this.traingDataCount = trainingData.get().count(); + } + + @Override + public Stream<Properties> get() { + final long partitionRest = traingDataCount % k; + + // calculate each partition size + final long partitionSizes[] = new long[k]; + for(int i = 0; i < k; ++i) { + partitionSizes[i] = traingDataCount / k + (i < partitionRest ? 1 : 0); + } + + // calculate counts + final long before = Arrays.stream(partitionSizes) + .limit(partition - 1) + .sum(); + final long skip = partitionSizes[partition - 1]; + + log.info("Streaming partition #{}. Properties before: {}. Properties to skip: {}.", partition, before, skip); + + final AtomicLong counter = new AtomicLong(0); + + return trainingData.get() + .filter(p -> { + long c = counter.incrementAndGet(); + + return c <= before || c > before + skip; + }); + } + + public PropertiesStreamSupplier getValidationStream() { + return new PropertiesStreamSupplier() { + @Override + public Stream<Properties> get() { + final long partitionRest = traingDataCount % k; + + // calculate each partition size + final long partitionSizes[] = new long[k]; + for(int i = 0; i < k; ++i) { + partitionSizes[i] = traingDataCount / k + (i < partitionRest ? 1 : 0); + } + + // calculate counts + final long before = Arrays.stream(partitionSizes) + .limit(partition - 1) + .sum(); + final long count = partitionSizes[partition - 1]; + + + log.info("Streaming validation partition #{}. Properties before: {}. Properties to stream: {}.", partition, before, count); + + final AtomicLong counter = new AtomicLong(0); + + return trainingData.get() + .filter(p -> { + long c = counter.incrementAndGet(); + + return c > before || c <= before + count; + }); + } + }; + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/rsquare/RSquareCalculator.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/rsquare/RSquareCalculator.java new file mode 100644 index 0000000000000000000000000000000000000000..deda8dce092b4664d93b68af481bc3a13b098e07 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/rsquare/RSquareCalculator.java @@ -0,0 +1,125 @@ +package de.evoal.surrogate.main.gof.rsquare; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.stream.PropertiesBasedPropertiesPairStreamSupplier; +import de.evoal.core.api.properties.stream.PropertiesPairStreamSupplier; +import de.evoal.core.api.properties.stream.PropertiesStreamSupplier; +import de.evoal.core.api.utils.Requirements; +import de.evoal.surrogate.api.SurrogateInformationCalculator; +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.SurrogateConfiguration; +import de.evoal.surrogate.api.function.SurrogateFunction; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.math3.stat.descriptive.moment.Mean; +import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation; +import org.apache.commons.math3.util.Pair; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; +import java.util.Arrays; +import java.util.List; + +/** + * Calculates cross validation values. + */ +@Dependent +@Named("R²") +@Slf4j +public class RSquareCalculator implements SurrogateInformationCalculator { + + /** + * Surrogate configuration for attaching the calculated r² value. + */ + private SurrogateConfiguration config; + + /** + * The actual surrogate function. + */ + private SurrogateFunction function; + + /** + * Supplier for the trainings data. + */ + private PropertiesStreamSupplier trainingData; + + @Override + public void execute() { + log.info("calculating r² of surrogate function."); + + final PropertiesPairStreamSupplier pairStream = new PropertiesBasedPropertiesPairStreamSupplier(trainingData, function.getInputSpecification(), function.getOutputSpecification()); + + final double [][] yValues = pairStream.get() + .map(Pair::getSecond) + .map(Properties::getValues) + .toArray(size -> new double[size][]); + + final double [] means = new double [yValues[0].length]; + for(int i = 0; i < means.length; ++i) { + final int j = i; + means[i] = new Mean().evaluate(Arrays.stream(yValues) + .mapToDouble(a -> a[j]) + .toArray()); + } + + final double [] sds = new double [yValues[0].length]; + for(int i = 0; i < sds.length; ++i) { + final int j = i; + sds[i] = new StandardDeviation().evaluate(Arrays.stream(yValues) + .mapToDouble(a -> a[j]) + .toArray()); + } + + final double [][] errors = + pairStream.get() + .map(pair -> { + final Properties input = pair.getFirst(); + final Properties calculated = function.apply(input); + + double [] values = new double[calculated.size()]; + System.arraycopy(calculated.getValues(), 0, values, 0, values.length); + + for(int i = 0; i < values.length; ++i) { + values[i] = Math.pow(pair.getSecond().get(i) - values[i], 2); + } + + return values; + }).toArray(size -> new double [size][]); + + for(int y = 0; y < errors[0].length; ++y) { + double error = 0.0; + + for(int x = 0; x < errors.length; ++x) { + error += errors[x][y]; + } + + final double unexplainedVariation = error; + final double totalVariation = Math.pow(sds[y], 2) * yValues.length; + final double rSquare = 1 - unexplainedVariation / totalVariation; + + log.info("unexplained variation: '{}', total variation: {}, rSquare: {}", unexplainedVariation, totalVariation, rSquare); + + final Parameter goodnessOfFit = Parameter.builder() + .name("r²") + .value(rSquare) + .build(); + + config.addOutputParameter(function.getOutputSpecification().getProperties().get(y).name(), goodnessOfFit); + } + } + + @Override + public String toString() { + return "r²"; + } + + @Override + public void configure(final SurrogateFunction function, final SurrogateConfiguration config, final List<Object> parameters, final PropertiesStreamSupplier trainingData) { + Requirements.requireEmpty(parameters); + Requirements.requireNotNull(function); + Requirements.requireNotNull(config); + + this.function = function; + this.config = config; + this.trainingData = trainingData; + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/rsquare/RSquareData.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/rsquare/RSquareData.java new file mode 100644 index 0000000000000000000000000000000000000000..846ceba8453257648d832477bc7886f9a7870a51 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/gof/rsquare/RSquareData.java @@ -0,0 +1,26 @@ +package de.evoal.surrogate.main.gof.rsquare; + +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import lombok.Data; + +@Data +public class RSquareData { + private final double value; + + public RSquareData(final double value) { + this.value = value; + } + public void attachTo(final PartialFunctionConfiguration regression, final String outputName) { + attach(regression, outputName, "r²", value); + } + + private void attach(final PartialFunctionConfiguration regression, final String output, final String name, final Object value) { + final Parameter parameter = Parameter.builder() + .name(name) + .value(value) + .build(); + + regression.addOutputParameter(output, parameter); + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/internal/StatementExecutor.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/internal/StatementExecutor.java new file mode 100644 index 0000000000000000000000000000000000000000..a6309e87a5c3b70ab46faa0a6d3bb5d415291467 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/internal/StatementExecutor.java @@ -0,0 +1,209 @@ +package de.evoal.surrogate.main.internal; + +import de.evoal.core.api.cdi.BeanFactory; +import de.evoal.core.api.utils.ConstantSwitch; +import de.evoal.languages.model.el.Call; +import de.evoal.languages.model.el.StringLiteral; +import de.evoal.languages.model.mll.*; +import de.evoal.languages.model.mll.util.MllSwitch; +import de.evoal.surrogate.api.SurrogateInformationCalculator; +import de.evoal.surrogate.api.configuration.SurrogateConfiguration; +import de.evoal.core.api.properties.stream.FileBasedPropertiesStreamSupplier; +import de.evoal.surrogate.api.function.SurrogateFunction; +import de.evoal.surrogate.api.training.TrainingDataManager; +import lombok.NonNull; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +import javax.enterprise.context.Dependent; +import javax.inject.Inject; +import javax.inject.Named; +import java.io.File; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +@Dependent +@Slf4j +public class StatementExecutor extends MllSwitch<Object> { + + private SurrogateConfiguration config; + + private SurrogateDefinition definition; + + private SurrogateFunction function; + + @Setter + private SymbolTable symbolTable = new SymbolTable(null); + + @Inject + private TrainingDataManager manager; + + + /** + * Variable pattern + */ + private final static Pattern varPattern = Pattern.compile("\\$\\{[^}]*}"); + + @Inject + @Named("surrogate-writer") + private BiConsumer<@NonNull SurrogateConfiguration, @NonNull File> surrogateWriter; + + public void evaluate(final Statement statement) { + doSwitch(statement); + } + + @Override + public Object caseCallStatement(final CallStatement call) { + log.info("Have to call stuff: {}", call.getCall().getFunction()); + + return null; + } + + @Override + public Object caseBlockStatement(final BlockStatement block) { + block.getStatements() + .forEach(this::evaluate); + + return null; + } + + @Override + public Object casePredictStatement(final PredictStatement predict) { + final File input = calculateFilename(predict.getTrainingData()); + final File output = calculateFilename(predict.getModelFilename()); + final SurrogateDefinition definition = predict.getSurrogate(); + + apply(definition, input, output, predict.getStatements()); + + return null; + } + + private void apply(final SurrogateDefinition definition, final File input, final File output, final List<CallStatement> statements) { + log.info("Applying {} to {}.", definition.getName(), input); + + final long startTime = System.currentTimeMillis(); + manager.setTrainingStream(new FileBasedPropertiesStreamSupplier(input)); + this.definition = definition; + this.config = SurrogateConfiguration.from(definition); + + log.info("Training surrogate function."); + this.function = new SurrogateFactory(config, manager.getTrainingStream()).create(); + + final long endTime = System.currentTimeMillis(); + log.info("Calculation of surrogate took " + (endTime - startTime) + " ms."); + + statements.forEach(this::handleGoodnessOfFitCall); + + saveTrainedSurrogateFunctionFunction(output); + } + + private void handleGoodnessOfFitCall(final CallStatement statement) { + final Call call = statement.getCall(); + final DefinedFunctionName function = (DefinedFunctionName)call.getFunction(); + log.info("Handling call of {} ...", function.getDefinition().getName()); + + log.info("Creating GOF instance."); + final SurrogateInformationCalculator calculator = BeanFactory.create(function.getDefinition().getName(), SurrogateInformationCalculator.class); + + log.info("Calculating parameter values."); + final List<Object> parameters = call.getParameters() + .stream() + .map(ConstantSwitch::findConstant) + .collect(Collectors.toList()); + + log.info("Configuring GOF instance."); + calculator.configure(this.function, config, parameters, manager.getTrainingStream()); + + log.info("Calling GOF instance."); + final long startTime = System.currentTimeMillis(); + calculator.execute(); + final long endTime = System.currentTimeMillis(); + + log.info("Calculation of {} took {} ms.", function.getDefinition().getName(), (endTime - startTime)); + } + + private File calculateFilename(String filename) { + final Matcher matcher = varPattern.matcher(filename); + + final Set<String> vars = new HashSet<>(); + while(matcher.find()) { + vars.add(filename.substring(matcher.start() + 2, matcher.end() - 1)); + } + + for(final String var : vars) { + String replacement = null; + + Object value = symbolTable.get(var); + if(value instanceof Integer) { + replacement = value.toString(); + } else if(value instanceof String) { + replacement = (String)value; + } + + if(replacement == null) { + log.warn("Unable to replace ${{}}", var); + continue; + } + + filename = filename.replace("${" + var + "}", replacement); + } + + return new File(filename); + } + + @Override + public Object caseForStatement(final ForStatement loop) { + final Stream<?> elements = (Stream<?>) doSwitch(loop.getRange()); + + elements.forEach(element -> { + final SymbolTable scopedTable = new SymbolTable(symbolTable); + scopedTable.put(loop.getName(), element); + + final StatementExecutor executor = BeanFactory.create(StatementExecutor.class); + executor.setSymbolTable(scopedTable); + loop.getStatements() + .forEach(executor::evaluate); + }); + + return null; + } + + @Override + public Object caseCounterRange(final CounterRange range) { + final boolean reverse = range.getStart() > range.getEnd(); + final int min = Math.min(range.getStart(), range.getEnd()); + final int max = Math.max(range.getStart(), range.getEnd()); + final int count = max - min; + + Stream<Integer> values = IntStream.rangeClosed(0, count) + .boxed(); + + if(reverse) { + values = values.map(i -> max - i); + } else { + values = values.map(i -> min + i); + } + + return values; + } + + @Override + public Object caseStringLiteralRange(final StringLiteralRange range) { + return range.getElements() + .stream() + .map(StringLiteral::getValue); + } + + private void saveTrainedSurrogateFunctionFunction(final File outputFilename) { + log.info("Storing pre-calculated predictive functions to '{}' ...", outputFilename); + surrogateWriter.accept(config, outputFilename); + log.info("Storing file was successful."); + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/internal/SurrogateFactory.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/internal/SurrogateFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..330c9da8cd2438e804449fac27c97f5070faeedf --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/internal/SurrogateFactory.java @@ -0,0 +1,110 @@ +package de.evoal.surrogate.main.internal; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import de.evoal.core.api.cdi.BeanFactory; +import de.evoal.core.api.properties.stream.PropertiesStreamSupplier; +import de.evoal.surrogate.api.configuration.FunctionCombinerConfiguration; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.configuration.SurrogateConfiguration; +import de.evoal.surrogate.api.function.FunctionCombiner; +import de.evoal.surrogate.api.function.PartialSurrogateFunction; +import de.evoal.surrogate.api.function.PartialSurrogateFunctionFactory; +import de.evoal.surrogate.api.function.SurrogateFunction; +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +import de.evoal.core.api.properties.PropertiesSpecification; + +/** + * Factory class for predictive functions. + */ +@Slf4j +public final class SurrogateFactory { + + /** + * Name of the predictive file + */ + @Getter + private final SurrogateConfiguration config; + + /** + * Training points used + */ + private final PropertiesStreamSupplier factory; + + public static SurrogateFunction create(final @NonNull SurrogateConfiguration config, final PropertiesStreamSupplier trainingPoints) { + final List<FunctionCombiner> functions = + config.getMappings() + .stream() + .map(layer -> createLayerFunction(layer, trainingPoints)) + .collect(Collectors.toList()); + + return new SurrogateFunction(functions); + } + + private static FunctionCombiner createLayerFunction(final FunctionCombinerConfiguration config, final PropertiesStreamSupplier trainingPoints) { + log.info("Creating mapping function for level {}.", config.getName()); + + final List<PartialFunctionConfiguration> subConfiguration = config.getFunctions(); + + // map source values to properties + final PropertiesSpecification sourceSpecification = + createProperties(subConfiguration.stream() + .flatMap(psf -> psf.getInputDimensions().stream())); + + // map source values to properties + final PropertiesSpecification targetSpecification = + createProperties(subConfiguration.stream() + .flatMap(psf -> psf.getOutputDimensions().stream())); + + final List<PartialSurrogateFunction> subFunctions = createFunctions(subConfiguration, sourceSpecification, targetSpecification, trainingPoints); + + return new FunctionCombiner(subFunctions, sourceSpecification, targetSpecification); + } + + private static List<PartialSurrogateFunction> createFunctions(final List<PartialFunctionConfiguration> configurations, final PropertiesSpecification source, final PropertiesSpecification target, final PropertiesStreamSupplier trainingPoints) { + // create regression functions from configuration + final List<PartialSurrogateFunction> functions = new ArrayList<>(configurations.size()); + + for(final PartialFunctionConfiguration config : configurations) { + functions.add(create(config, source, target, trainingPoints)); + } + + return functions; + } + + public static PartialSurrogateFunction create(final PartialFunctionConfiguration config, final PropertiesSpecification source, final PropertiesSpecification target, final PropertiesStreamSupplier trainingPoints) { + log.info("Calculating mapping function '{}'.", config.getName()); + final PropertiesSpecification functionTargetSpecification = + PropertiesSpecification.builder() + .add(config.getOutputDimensions().stream()) + .build(); + + final PropertiesSpecification functionSourceSpecification = + PropertiesSpecification.builder() + .add(config.getInputDimensions().stream()) + .build(); + + final PartialSurrogateFunctionFactory factory = BeanFactory.create(config.getName(), PartialSurrogateFunctionFactory.class); + return factory.create(config, source, functionSourceSpecification, functionTargetSpecification, trainingPoints); + } + + private static PropertiesSpecification createProperties(final Stream<String> descriptors) { + return PropertiesSpecification.builder() + .add(descriptors) + .build(); + } + + public SurrogateFactory(final SurrogateConfiguration config, final PropertiesStreamSupplier factory) { + this.config = config; + this.factory = factory; + } + + public SurrogateFunction create() { + return create(config, factory); + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/internal/SymbolTable.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/internal/SymbolTable.java new file mode 100644 index 0000000000000000000000000000000000000000..d6d4cb061236cded28a324831e10b770e27b497a --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/internal/SymbolTable.java @@ -0,0 +1,29 @@ +package de.evoal.surrogate.main.internal; + +import java.util.HashMap; +import java.util.Map; + +public class SymbolTable { + private final SymbolTable parentTable; + private Map<String, Object> bindings = new HashMap<>(); + + public SymbolTable(final SymbolTable symbolTable) { + this.parentTable = symbolTable; + } + + public void put(final String name, final Object value) { + this.bindings.put(name, value); + } + + public Object get(final String name) { + if(bindings.containsKey(name)) { + return bindings.get(name); + } + + if(parentTable != null) { + return parentTable.get(name); + } + + return null; + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/jackson/ReflectiveDeserializer.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/jackson/ReflectiveDeserializer.java new file mode 100644 index 0000000000000000000000000000000000000000..0659f9ccb3d3b7e022dbb2977beada82b639689f --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/jackson/ReflectiveDeserializer.java @@ -0,0 +1,252 @@ +package de.evoal.surrogate.main.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import smile.math.matrix.Matrix; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ReflectiveDeserializer extends StdDeserializer<Object> { + protected ReflectiveDeserializer() { + super(Object.class); + } + + @Override + public Object deserialize(final JsonParser parser, final DeserializationContext context) throws IOException, JsonProcessingException { + return readObject(parser); + } + + private Object readObject(final JsonParser parser) throws IOException, JsonProcessingException { + assertTokenTypeAndAdvance(parser, JsonToken.START_OBJECT); + + final JsonToken nextValue = parser.nextValue(); + assertFieldName(parser.getCurrentName(), "type"); + + Object value = null; + final String type = parser.getValueAsString(); + switch (type) { + case "string": + value = getStringValue(parser); + break; + + case "double": + value = getDoubleValue(parser); + break; + + case "integer": + value = getIntegerValue(parser); + break; + + case "array(double)": + value = getDoubleArrayValue(parser); + break; + + case "array(array(double))": + value = getDoubleArrayArrayValue(parser); + break; + + case "matrix": + value = getMatrixValue(parser); + break; + + case "dict": + value = getDictValue(parser); + break; + + case "array": + value = getArrayValue(parser); + break; + + default: + System.err.println("Unsupported type: " + type); + throw new IllegalArgumentException(); + } + + while(!parser.hasToken(JsonToken.END_OBJECT)) { + parser.nextToken(); + } + + return value; + } + + private Object getDictValue(final JsonParser parser) throws IOException { + final Map<String, Object> dict = new HashMap<>(); + + parser.nextValue(); + assertTokenTypeAndAdvance(parser, JsonToken.START_OBJECT); + + while(!JsonToken.END_OBJECT.equals(parser.getCurrentToken())) { + final String name = parser.getCurrentName(); + parser.nextToken(); + final Object value = readObject(parser); + parser.nextToken(); + + dict.put(name, value); + } + + parser.nextToken(); + return dict; + } + + private List<Object> getArrayValue(final JsonParser parser) throws IOException { + final List<Object> result = new ArrayList<>(); + + parser.nextValue(); + assertTokenTypeAndAdvance(parser, JsonToken.START_ARRAY); + + while(!JsonToken.END_ARRAY.equals(parser.getCurrentToken())) { + final Object value = readObject(parser); + parser.nextToken(); + + result.add(value); + } + + parser.nextToken(); + return result; + } + + private Object getDoubleArrayValue(final JsonParser parser) throws IOException { + parser.nextValue(); + assertFieldName(parser.getCurrentName(), "size"); + final int size = parser.getValueAsInt(); + + parser.nextValue(); + assertFieldName(parser.getCurrentName(), "value"); + + parser.nextToken(); + + final double [] result = new double[size]; + for(int i = 0; i < size; ++i) { + parser.nextValue(); + result[i] = parser.getValueAsDouble(); + } + assertArrayEnd(parser); + + return result; + } + + private Object getDoubleArrayArrayValue(final JsonParser parser) throws IOException { + parser.nextValue(); + assertFieldName(parser.getCurrentName(), "size-1"); + final int size1 = parser.getValueAsInt(); + + parser.nextValue(); + assertFieldName(parser.getCurrentName(), "size-2"); + final int size2 = parser.getValueAsInt(); + + parser.nextValue(); + assertFieldName(parser.getCurrentName(), "value"); + + parser.nextToken(); + + final double [][] result = new double[size1][size2]; + for(int i = 0; i < size1; ++i) { + for(int j = 0; j < size2; ++j) { + parser.nextValue(); + result[i][j] = parser.getValueAsDouble(); + } + } + assertArrayEnd(parser); + + return result; + } + + private Object getMatrixValue(final JsonParser parser) throws IOException { + parser.nextValue(); + assertFieldName(parser.getCurrentName(), "nrows"); + final int nrows = parser.getValueAsInt(); + + parser.nextValue(); + assertFieldName(parser.getCurrentName(), "ncols"); + final int ncols = parser.getValueAsInt(); + + parser.nextValue(); + assertFieldName(parser.getCurrentName(), "value"); + + parser.nextToken(); + + final Matrix result = new Matrix(nrows, ncols); + for(int i = 0; i < nrows; ++i) { + for(int j = 0; j < ncols; ++j) { + parser.nextValue(); + result.set(i, j, parser.getValueAsDouble()); + } + } + assertArrayEnd(parser); + + return result; + } + + private void assertArrayStart(final JsonParser parser) throws IOException { + assertTokenTypeAndAdvance(parser, JsonToken.START_ARRAY); + } + + private void assertArrayEnd(final JsonParser parser) throws IOException { + assertTokenTypeAndAdvance(parser, JsonToken.END_ARRAY); + } + + private Object getStringValue(final JsonParser parser) throws IOException { + parser.nextValue(); + assertFieldName(parser.getCurrentName(), "value"); + + return parser.getValueAsString(); + } + + private Object getDoubleValue(final JsonParser parser) throws IOException { + final JsonToken nextValue = parser.nextValue(); + assertFieldName(parser.getCurrentName(), "value"); + + return parser.getValueAsDouble(); + } + + private Object getIntegerValue(final JsonParser parser) throws IOException { + final JsonToken nextValue = parser.nextValue(); + assertFieldName(parser.getCurrentName(), "value"); + + return parser.getValueAsInt(); + } + + private void assertFieldName(final String actual, final String expected) { + if(!expected.equals(actual)) { + throw new IllegalStateException("Expected field name " + expected + " but got " + actual); + } + } + + private String getStringAndAdvance(final JsonParser parser) throws IOException { + if(!JsonToken.VALUE_STRING.equals(parser.getCurrentToken())) { + throw new IllegalStateException("Expected token " + JsonToken.VALUE_STRING + " got " + parser.getCurrentToken()); + } + + final String value = parser.getText(); + + parser.nextToken(); + + return value; + } + + private void assertFieldAndAdvance(final JsonParser parser, final String name) throws IOException { + if(!JsonToken.FIELD_NAME.equals(parser.getCurrentToken())) { + throw new IllegalStateException("Expected token " + JsonToken.FIELD_NAME + " got " + parser.getCurrentToken()); + } + + if(!name.equals(parser.getText())) { + throw new IllegalStateException("Expected field " + name + " got " + parser.getText()); + } + + parser.nextToken(); + } + + private void assertTokenTypeAndAdvance(final JsonParser parser, final JsonToken token) throws IOException { + if(!token.equals(parser.getCurrentToken())) { + throw new IllegalStateException("Expected token " + token + " got " + parser.getCurrentToken()); + } + parser.nextToken(); + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/jackson/ReflectiveSerializer.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/jackson/ReflectiveSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..b249e7efe69182147c5cc36bd762433973b7d827 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/jackson/ReflectiveSerializer.java @@ -0,0 +1,130 @@ +package de.evoal.surrogate.main.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import smile.math.matrix.Matrix; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class ReflectiveSerializer extends StdSerializer<Object> { + protected ReflectiveSerializer() { + super(Object.class); + } + + @Override + public void serialize(final Object o, final JsonGenerator jsonGenerator, final SerializerProvider serializerProvider) throws IOException { + if(o instanceof Double) { + serializeDouble((Double) o, jsonGenerator); + } else if(o instanceof Integer) { + serializeInteger((Integer)o, jsonGenerator); + } else if(o instanceof String) { + serializeString((String)o, jsonGenerator); + } else if(o instanceof double [][]) { + serializeArray((double[][])o, jsonGenerator); + } else if(o instanceof double []) { + serializeArray((double[])o, jsonGenerator); + } else if(o instanceof Matrix) { + serializeArray((Matrix)o, jsonGenerator); + } else if(o instanceof List) { + serializeList((List<?>)o, jsonGenerator, serializerProvider); + } else if(o instanceof Map) { + serializeMap((Map<String, ?>)o, jsonGenerator, serializerProvider); + } + } + + private void serializeDouble(final Double o, final JsonGenerator jsonGenerator) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("type", "double"); + jsonGenerator.writeNumberField("value", o); + jsonGenerator.writeEndObject(); + } + + private void serializeInteger(final Integer o, final JsonGenerator jsonGenerator) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("type", "integer"); + jsonGenerator.writeNumberField("value", o); + jsonGenerator.writeEndObject(); + } + + private void serializeString(final String o, final JsonGenerator jsonGenerator) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("type", "string"); + jsonGenerator.writeStringField("value", o); + jsonGenerator.writeEndObject(); + } + + private void serializeArray(final double [] o, final JsonGenerator jsonGenerator) throws IOException { + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("type", "array(double)"); + jsonGenerator.writeNumberField("size", o.length); + jsonGenerator.writeFieldName("value"); + jsonGenerator.writeArray(o, 0, o.length); + jsonGenerator.writeEndObject(); + } + + private void serializeArray(final double [][] o, final JsonGenerator jsonGenerator) throws IOException { + if(o.length == 0) { + System.err.println("Here we are"); + } + + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("type", "array(array(double))"); + jsonGenerator.writeNumberField("size-1", o.length); + jsonGenerator.writeNumberField("size-2", o.length == 0 ? 0 : o[0].length); + jsonGenerator.writeFieldName("value"); + jsonGenerator.writeStartArray(o.length); + for(int i = 0; i < o.length; ++i) { + for(int j = 0; j < o[i].length; ++j) { + jsonGenerator.writeNumber(o[i][j]); + } + } + jsonGenerator.writeEndArray(); + jsonGenerator.writeEndObject(); + } + + private void serializeArray(final Matrix o, final JsonGenerator jsonGenerator) throws IOException { + final int nrows = o.nrows(); + final int ncols = o.ncols(); + + jsonGenerator.writeStartObject(); + jsonGenerator.writeStringField("type", "matrix"); + + jsonGenerator.writeNumberField("nrows", nrows); + jsonGenerator.writeNumberField("ncols", ncols); + jsonGenerator.writeFieldName("value"); + jsonGenerator.writeStartArray(nrows * ncols); + for(int i = 0; i < nrows; ++i) { + for(int j = 0; j < ncols; ++j) { + jsonGenerator.writeNumber(o.get(i, j)); + } + } + jsonGenerator.writeEndArray(); + jsonGenerator.writeEndObject(); + } + + private void serializeList(final List<?> list, final JsonGenerator generator, final SerializerProvider provider) throws IOException { + generator.writeStartArray(); + + boolean first = true; + for(final Object child : list) { + serialize(child, generator, provider); + } + + generator.writeEndArray(); + } + + private void serializeMap(final Map<String, ?> map, final JsonGenerator generator, final SerializerProvider provider) throws IOException { + generator.writeStartObject(); + + boolean first = true; + for(final Map.Entry<String, ?> child : map.entrySet()) { + generator.writeFieldName(child.getKey()); + serialize(child.getValue(), generator, provider); + } + + generator.writeEndObject(); + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/jackson/package-info.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/jackson/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..175cdda8003adff740178e683f732da19ac06cfa --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/jackson/package-info.java @@ -0,0 +1,4 @@ +/** + * Classes that helps (de-)serializing a surrogate function to JSON using Jackson. + */ +package de.evoal.surrogate.main.jackson; \ No newline at end of file diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/statistics/correlated/GenerationStatisticsWriter.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/statistics/correlated/GenerationStatisticsWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..1e5d6823724dee69a104b3935d6a5cd0cca72271 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/statistics/correlated/GenerationStatisticsWriter.java @@ -0,0 +1,314 @@ +package de.evoal.surrogate.main.statistics.correlated; + +import de.evoal.core.api.statistics.*; +import de.evoal.core.api.utils.LanguageHelper; +import de.evoal.languages.model.instance.Instance; +import de.evoal.core.api.board.BlackboardEntry; +import de.evoal.core.api.cdi.ConfigurationValue; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.surrogate.api.training.TrainingDataManager; +import io.jenetics.Genotype; +import io.jenetics.engine.EvolutionResult; +import io.jenetics.util.ISeq; +import javax.annotation.PostConstruct; +import javax.enterprise.context.Dependent; +import lombok.extern.slf4j.Slf4j; + +import org.apache.commons.math3.util.Pair; +import smile.math.matrix.Matrix; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Small helper class for collecting and writing the generation-based statistics. + */ +@Slf4j +@Named("correlated") +@Dependent +public class GenerationStatisticsWriter implements StatisticsWriter { + /** + * List of all existing fitness functions. + */ + private List<Function<Properties, FitnessType>> functions; + + /** + * List of all function names + */ + @Inject @Named("function-names") + private List<String> functionNames; + + /** + * Encoding for converting between ea and domain. + */ + @Inject + private CustomCodec encoding; + + private long startTime; + + private EvolutionResult<?, FitnessType> generationWithBestIndividual; + private long endTime; + + @Inject + private Provider<Function<Properties, FitnessType>> fitnessFactory; + + @Inject @ConfigurationValue(entry = BlackboardEntry.EA_CONFIGURATION, access = "algorithm.fitness") + private Instance config; + + @Inject + private TrainingDataManager manager; + + @Inject @Named("source-properties-specification") + private PropertiesSpecification sourceSpec; + + @Inject @Named("target-properties-specification") + private PropertiesSpecification targetSpec; + private List<Properties> sourceTrainingPoints; + + /** + * Creates a new GenerationStatistics instance. + */ + + @Inject + private WriterStrategy strategy; + + @PostConstruct + public void init() { + final String selectedFunctionName = LanguageHelper.lookup(config, "name"); + + startTime = System.currentTimeMillis(); + // create fitness functions for comparison + this.functionNames.sort((a, b) -> { + if(selectedFunctionName.equals(a)) { + return Integer.MIN_VALUE; + } else if(selectedFunctionName.equals(b)) { + return Integer.MAX_VALUE; + } else { + return String.CASE_INSENSITIVE_ORDER.compare(a, b); + } + }); + this.functions = functionNames.stream() + .map(name -> { + setFitnessType(config, name); + return fitnessFactory.get(); + }) + .collect(Collectors.toList()); + + // restore fitness function + setFitnessType(config, selectedFunctionName); + } + + @Override + public StatisticsWriter init(Instance configuration) { + return this; + } + + private Matrix calculateCovarianceMatrix(final Matrix data) { + final int dimensions = data.nrows(); + final int dataSize = data.ncols(); + final double [] means = data.rowMeans(); + + final Matrix covarianceMatrix = new Matrix(dimensions, dimensions); + for(int x = 0; x < dimensions; ++x) { + for(int y = 0; y < dimensions; ++y) { + double value = 0.0; + for(int t = 0; t < dataSize; ++t) { + value += ((data.get(x, t) - means[x])*(data.get(y, t) - means[y])); + } + value = value / dataSize; + covarianceMatrix.set(x, y, value); + } + } + + return covarianceMatrix; + } + + private void calculateCovariance(final Properties candidate, final Object[] data, final int index) { + final int dimensions = sourceSpec.size(); + final int trainingsSize = sourceTrainingPoints.size(); + final int onTheFlySize = trainingsSize + 1; + + final Matrix trainingsMatrix = new Matrix(dimensions, trainingsSize); + final Matrix onTheFlyMatrix = new Matrix(dimensions, onTheFlySize); + + // calculate covariance matrix for trainings data + for(int t = 0; t < trainingsSize; t++) { + for(int d = 0; d < dimensions; ++d) { + final double value = sourceTrainingPoints + .get(t) + .get(d); + trainingsMatrix.set(d, t, value); + onTheFlyMatrix.set(d, t, value); + } + } + + for(int d = 0; d < dimensions; ++d) { + onTheFlyMatrix.set(d, onTheFlySize - 1, candidate.get(d)); + } + + final Matrix trainingsCovarianceMatrix = calculateCovarianceMatrix(trainingsMatrix); + + { + final Matrix onTheFlyCovarianceMatrix = calculateCovarianceMatrix(onTheFlyMatrix); + calculateDifference(new Matrix(trainingsCovarianceMatrix.toArray()), onTheFlyCovarianceMatrix, data, index); + } + + { + final Matrix bestGenerationMatrix = createBestGenerationMatrix(); + final Matrix bestGenerationCovariance = calculateCovarianceMatrix(bestGenerationMatrix); + calculateDifference(new Matrix(trainingsCovarianceMatrix.toArray()), bestGenerationCovariance, data, index + dimensions * dimensions + 2); + } + } + + private Matrix createBestGenerationMatrix() { + final ISeq<Genotype<?>> genotypes = (ISeq<Genotype<?>>)(Object)generationWithBestIndividual.genotypes(); + + final int dimensions = sourceSpec.size(); + final int size = genotypes.size(); + + final Matrix result = new Matrix(dimensions, size); + + for(int i = 0; i < size; ++i) { + final Genotype<?> genotype = genotypes.get(i); + final Properties individual = (Properties) encoding.decode(genotype); + + final double [] data = individual.getValues(); + + for(int j = 0; j < data.length; ++j) { + result.set(i, j, data[j]); + } + } + + return result; + } + + private void calculateDifference(final Matrix matrix1, final Matrix matrix2, final Object [] data, final int start) { + final Matrix differenceMatrix = matrix1.sub(matrix2); + double sumOfAbs = 0.0; + double sumOfSquares = 0.0; + + for(int x = 0; x < differenceMatrix.nrows(); ++x) { + for (int y = 0; y < differenceMatrix.ncols(); ++y) { + data[start + x * differenceMatrix.nrows() + y] = differenceMatrix.get(x,y); + sumOfAbs += Math.abs(differenceMatrix.get(x, y)); + sumOfSquares += (Math.pow(differenceMatrix.get(x, y), 2)); + } + } + + data[start + differenceMatrix.nrows() * differenceMatrix.ncols()] = sumOfAbs; + data[start + differenceMatrix.nrows() * differenceMatrix.ncols() + 1] = sumOfSquares; + } + + private Writer createWriter() throws WriterException { + final List<Column> columns = new ArrayList<>(); + + columns.add(new Column("generation", ColumnType.Integer)); + columns.add(new Column("individual", ColumnType.String)); + columns.add(new Column("age", ColumnType.Integer)); + + final int nmrOfFunctions = functionNames.size(); + for (int i = 0; i < nmrOfFunctions; ++i) { + columns.add(new Column(functionNames.get(i), ColumnType.Double)); + } + + for (int x = 0; x < sourceSpec.size(); ++x) { + for (int y = 0; y < sourceSpec.size(); ++y) { + columns.add(new Column("ind. difference(" + x + "/" + y + ")", ColumnType.Double)); + } + } + columns.add(new Column("sum(abs(ind. difference))", ColumnType.Double)); + columns.add(new Column("sum(sqr(ind. difference))", ColumnType.Double)); + + for (int x = 0; x < sourceSpec.size(); ++x) { + for (int y = 0; y < sourceSpec.size(); ++y) { + columns.add(new Column("pop. difference(" + x + "/" + y + ")", ColumnType.Double)); + } + } + columns.add(new Column("sum(abs(pop. difference))", ColumnType.Double)); + columns.add(new Column("sum(sqr(pop. difference))", ColumnType.Double)); + + columns.add(new Column("time", ColumnType.Integer)); + + return strategy.create("best-individual-statistics", columns); + } + + private Object[] dataOfBest() { + fetchTrainingData(); + + final Object [] data = new Object[3 + functionNames.size() + (int)Math.pow(sourceSpec.size(), 2) + 2 + (int)Math.pow(sourceSpec.size(), 2) + 2 + 1]; + + final Genotype<?> genotype = generationWithBestIndividual.bestPhenotype().genotype(); + final Properties individual = (Properties) encoding.decode(genotype); + + data[0] = generationWithBestIndividual.generation(); + data[1] = Arrays.toString(individual.getValues()); + data[2] = generationWithBestIndividual.bestPhenotype().age(generationWithBestIndividual.generation()); + + final Properties candidate = (Properties) encoding.decode(generationWithBestIndividual.bestPhenotype().genotype()); + + for(int i = 0; i < functions.size(); ++i) { + final FitnessType fitness = functions.get(i).apply(candidate); + data[3 + i] = fitness.getFitnessValues()[0]; + throw new IllegalArgumentException("fix me"); + } + + calculateCovariance(candidate, data, 3 + functions.size()); + + data[data.length - 1] = endTime - startTime; + + return data; + } + + private void fetchTrainingData() { + final PropertiesSpecification spec = PropertiesSpecification.builder() + .add(sourceSpec) + .add(targetSpec) + .build(); + + sourceTrainingPoints = manager.getTrainingStream() + .apply(spec) + .collect(Collectors.toList()); + } + + /** + * Changes the fitness type entry in the given config object. + */ + private static void setFitnessType(final Instance config, final String name) { + //config.put("name", name); + throw new IllegalStateException("We have to change the actual fitness type by looking up the correct definition."); + } + + public void add(final EvolutionResult<?, FitnessType> evolutionResult) { + updateBestGeneration(evolutionResult); + } + + private void updateBestGeneration(final EvolutionResult<?, FitnessType> generation) { + if(generationWithBestIndividual == null) { + generationWithBestIndividual = generation; + } else if(generationWithBestIndividual.bestFitness().compareTo(generation.bestFitness()) <= 0) { + generationWithBestIndividual = generation; + } + } + + public void write() { + endTime = System.currentTimeMillis(); + try { + final Writer writer = createWriter(); + + writer.addRecord(dataOfBest()); + + writer.close(); + } catch (final WriterException e) { + log.error("Failed to write statistics:", e); + } + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/statistics/surrogate/SurrogateStatistics.java b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/statistics/surrogate/SurrogateStatistics.java new file mode 100644 index 0000000000000000000000000000000000000000..0bfd4e0288ce050cfa39bc87b54fc8d421f4e861 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/de/evoal/surrogate/main/statistics/surrogate/SurrogateStatistics.java @@ -0,0 +1,109 @@ +package de.evoal.surrogate.main.statistics.surrogate; + +import java.util.LinkedList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.Dependent; +import javax.inject.Inject; +import javax.inject.Named; + +import de.evoal.core.api.statistics.*; +import de.evoal.core.api.ea.codec.CustomCodec; +import de.evoal.core.api.ea.fitness.type.FitnessType; +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.languages.model.instance.Instance; +import de.evoal.surrogate.api.function.SurrogateFunction; +import io.jenetics.Genotype; +import io.jenetics.Phenotype; +import io.jenetics.engine.EvolutionResult; +import io.jenetics.util.ISeq; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; + +/** + * Small helper class for collecting and writing the generation-based statistics. + */ +@Slf4j +@Named("prediction-per-individual") +@Dependent +public class SurrogateStatistics implements StatisticsWriter { + /** + * The predictive function used. + */ + @Inject + protected SurrogateFunction predictive; + + /** + * Encoding for converting between ea and domain. + */ + @Inject + private CustomCodec encoding; + + @Inject @Named("target-properties-specification") + private PropertiesSpecification targetSpecification; + + @Inject + private WriterStrategy strategy; + + private Writer writer; + + @PostConstruct + @SneakyThrows(WriterException.class) + private void init() { + createWriter(); + } + + @Override + public StatisticsWriter init(Instance configuration) { + return this; + } + + private void createWriter() throws WriterException { + final List<Column> columns = new LinkedList<>(); + + columns.add(new Column("generation", ColumnType.Integer)); + columns.add(new Column("index", ColumnType.Integer)); + + for(int i = 0; i < targetSpecification.size(); ++i) { + columns.add(new Column(targetSpecification.getProperties().get(i).name(), ColumnType.Double)); + } + + writer = strategy.create("prediction-by-individual", columns); + } + + private Object[] dataOfPhenotype(final int index, final long generation, Phenotype<?, FitnessType> phenotype) { + final Object [] data = new Object[2 + targetSpecification.size()]; + + data[0] = generation; + data[1] = index; + + final Genotype<?> genotype = phenotype.genotype(); + final Properties individual = (Properties) encoding.decode(genotype); + final Properties predicted = predictive.apply(individual); + + for(int i = 0; i < predicted.size(); ++i) { + data[2 + i] = predicted.getValues()[i]; + } + + return data; + } + + @SneakyThrows(WriterException.class) + public void add(final EvolutionResult<?, FitnessType> evolutionResult) { + final ISeq<Phenotype<?, FitnessType>> population = (ISeq<Phenotype<?, FitnessType>>)(Object)evolutionResult.population(); + + for(int i = 0; i < population.size(); ++i) { + writer.addRecord(dataOfPhenotype(i, evolutionResult.generation(), population.get(i))); + } + } + + public void write() { + try { + strategy.close(writer); + } catch (final WriterException e) { + log.error("Failed to write statistics:", e); + } + } +} diff --git a/src/core/de.evoal.surrogate.api/src/main/java/module-info.java b/src/core/de.evoal.surrogate.api/src/main/java/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..87b54c6f417d3ddb87c6427eaab8fac2e8fbd6ee --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/java/module-info.java @@ -0,0 +1,46 @@ +module de.evoal.surrogate.api { + requires jakarta.inject.api; + requires jakarta.enterprise.cdi.api; + + requires java.annotation; + + requires lombok; + + requires org.slf4j; + + requires org.eclipse.emf.ecore; + requires org.eclipse.emf.common; + requires guice; + requires org.eclipse.xtext; + requires com.fasterxml.jackson.databind; + requires smile.math; + requires commons.math3; + requires io.jenetics.base; + + + requires de.evoal.core.api; + requires de.evoal.languages.model.ddl; + requires de.evoal.languages.model.dl; + requires de.evoal.languages.model.el; + requires de.evoal.languages.model.mll; + requires de.evoal.languages.model.ddl.dsl; + requires de.evoal.languages.model.dl.dsl; + requires de.evoal.languages.model.el.dsl; + requires de.evoal.languages.model.mll.dsl; + requires de.evoal.languages.model.instance; + + exports de.evoal.surrogate.api; + exports de.evoal.surrogate.api.function; + exports de.evoal.surrogate.api.configuration; + + // open packages for CDI + opens de.evoal.surrogate.api.training to weld.core.impl; + opens de.evoal.surrogate.main to weld.core.impl; + opens de.evoal.surrogate.main.cdi to weld.core.impl; + opens de.evoal.surrogate.main.internal to weld.core.impl; + opens de.evoal.surrogate.main.jackson to weld.core.impl; + opens de.evoal.surrogate.main.gof.cross to weld.core.impl; + opens de.evoal.surrogate.main.gof.rsquare to weld.core.impl; + opens de.evoal.surrogate.main.statistics.correlated to weld.core.impl; + opens de.evoal.surrogate.main.statistics.surrogate to weld.core.impl; +} \ No newline at end of file diff --git a/src/core/de.evoal.surrogate.api/src/main/resources/META-INF/MANIFEST.MF b/src/core/de.evoal.surrogate.api/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000000000000000000000000000000000000..9d885be534121a9f146924f4832955dfe2ee2d4b --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1 @@ +Manifest-Version: 1.0 diff --git a/src/core/de.evoal.surrogate.api/src/main/resources/META-INF/beans.xml b/src/core/de.evoal.surrogate.api/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000000000000000000000000000000000..848dca3b29cc3f1f9879d2c86d586612e5d8b3e7 --- /dev/null +++ b/src/core/de.evoal.surrogate.api/src/main/resources/META-INF/beans.xml @@ -0,0 +1,6 @@ +<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" + bean-discovery-mode="annotated" + version="2.0"> +</beans> \ No newline at end of file diff --git a/src/core/de.evoal.surrogate.simple/pom.xml b/src/core/de.evoal.surrogate.simple/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..578b14b1c7df714bd63d828a41c871061c37f5ba --- /dev/null +++ b/src/core/de.evoal.surrogate.simple/pom.xml @@ -0,0 +1,72 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>de.evoal.core</groupId> + <artifactId>releng.parent</artifactId> + <version>0.9.0-SNAPSHOT</version> + + <relativePath>../de.evoal.core.releng.parent</relativePath> + </parent> + + <artifactId>surrogate.simple</artifactId> + <name>EvoAl - Surrogate - Simple</name> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>core.api</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>surrogate.api</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-math3</artifactId> + <version>3.6.1</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>copy-dependencies</goal> + </goals> + <configuration> + <outputDirectory>${project.build.directory}/${project.artifactId}-dependencies</outputDirectory> + <includeScope>runtime</includeScope> + <excludeScope>provided</excludeScope> + <excludeTransitive>true</excludeTransitive> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifest> + <addClasspath>true</addClasspath> + <classpathPrefix>${project.artifactId}-dependencies/</classpathPrefix> + </manifest> + </archive> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/identity/IdentityFunction.java b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/identity/IdentityFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..b04cccc2407f8fcf5a7a3e9084020f91325baf2d --- /dev/null +++ b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/identity/IdentityFunction.java @@ -0,0 +1,29 @@ +package de.evoal.surrogate.simple.identity; + +import java.util.Collections; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.function.AbstractPartialSurrogateFunction; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class IdentityFunction extends AbstractPartialSurrogateFunction { + + private final int propertyIndex; + + public IdentityFunction(final PartialFunctionConfiguration configuration, final PropertiesSpecification input, final PropertiesSpecification actualInput, final PropertiesSpecification output) { + super(configuration, Collections.emptyList(), input, output); + + log.info("Using identity mapping from {} to {}.", input, output); + + final PropertySpecification inputProperty = input.getProperties().get(0); + propertyIndex = actualInput.indexOf(inputProperty); + } + + public double [] apply(final Properties input) { + return new double[] {input.get(propertyIndex)}; + } +} diff --git a/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/identity/IdentityFunctionFactory.java b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/identity/IdentityFunctionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..7fa982894bb28aef6c61e45db3f80902c156204b --- /dev/null +++ b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/identity/IdentityFunctionFactory.java @@ -0,0 +1,27 @@ +package de.evoal.surrogate.simple.identity; + +import java.util.List; + +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.stream.PropertiesPairStreamSupplier; +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.function.AbstractPartialSurrogateFunctionFactory; +import de.evoal.surrogate.api.function.PartialSurrogateFunction; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("identity") +public final class IdentityFunctionFactory extends AbstractPartialSurrogateFunctionFactory { + @Override + protected PartialSurrogateFunction calculateRegression(final PartialFunctionConfiguration configuration, final List<Parameter> parameters, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput, final PropertiesPairStreamSupplier provider) { + return new IdentityFunction(configuration, requiredInput, actualInput, producedOutput); + } + + @Override + protected PartialSurrogateFunction restoreRegression(final PartialFunctionConfiguration configuration, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput) { + return new IdentityFunction(configuration, requiredInput, actualInput, producedOutput); + } +} diff --git a/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/linear/LinearFunction.java b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/linear/LinearFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..0db00afcc0587bf3090a077f030e02dd25557f56 --- /dev/null +++ b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/linear/LinearFunction.java @@ -0,0 +1,86 @@ +package de.evoal.surrogate.simple.linear; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +import de.evoal.core.api.properties.Properties; +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.function.AbstractPartialSurrogateFunction; + +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.math3.stat.regression.SimpleRegression; + +@Slf4j +public class LinearFunction extends AbstractPartialSurrogateFunction { + /** + * Name of the intercept parameter. + */ + private static final String INTERCEPT_PARAMETER_NAME = "intercept"; + + /** + * Name of the r square parameter. + */ + private static final String R_SQUARE_PARAMETER_NAME = "r²"; + + /** + * Name of the slope parameter. + */ + private static final String SLOPE_PARAMETER_NAME = "slope"; + + /** + * Turns an Apache simple regression into a parameter set. + */ + public static List<Parameter> toParameters(final SimpleRegression regression) { + final List<Parameter> result = new LinkedList<>(); + + addParameter(INTERCEPT_PARAMETER_NAME, regression.getIntercept(), result); + addParameter(SLOPE_PARAMETER_NAME, regression.getSlope(), result); + addParameter(R_SQUARE_PARAMETER_NAME, regression.getRSquare(), result); + + return result; + } + + /** + * Actual function for prediction. + */ + private final Function<Properties, Double> regression; + + public LinearFunction(final PartialFunctionConfiguration configuration, final List<Parameter> functionParameters, final PropertiesSpecification input, final PropertiesSpecification actualInput, final PropertiesSpecification output) { + super(configuration, functionParameters, input, output); + + final double slope = getSlope(); + final double intercept = getIntercept(); + + log.info("Using linear regression f(x) = {} * x + {}.", slope, intercept); + + + final PropertySpecification inputProperty = input.getProperties().get(0); + final int propertyIndex = actualInput.indexOf(inputProperty); + + this.regression = vector -> intercept + slope * vector.get(propertyIndex); + } + + public double [] apply(final Properties input) { + return new double [] {regression.apply(input)}; + } + + private double getIntercept() { + return (double)getParameters().stream() + .filter(p -> INTERCEPT_PARAMETER_NAME.equals(p.getName())) + .findFirst() + .get() + .getValue(); + } + + private double getSlope() { + return (double)getParameters().stream() + .filter(p -> SLOPE_PARAMETER_NAME.equals(p.getName())) + .findFirst() + .get() + .getValue(); + } +} diff --git a/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/linear/LinearFunctionFactory.java b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/linear/LinearFunctionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..8e961f5b4023d26726851af25ff95fc745435c24 --- /dev/null +++ b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/linear/LinearFunctionFactory.java @@ -0,0 +1,45 @@ +package de.evoal.surrogate.simple.linear; + + +import de.evoal.core.api.properties.stream.PropertiesPairStreamSupplier; +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.function.AbstractPartialSurrogateFunctionFactory; +import de.evoal.surrogate.api.function.PartialSurrogateFunction; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.math3.stat.regression.SimpleRegression; + +import de.evoal.core.api.properties.PropertiesSpecification; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; +import java.util.List; + +@Dependent +@Named("linear-regression") +@Slf4j +public class LinearFunctionFactory extends AbstractPartialSurrogateFunctionFactory { + + @Override + public PartialSurrogateFunction calculateRegression(final PartialFunctionConfiguration configuration, final List<Parameter> parameters, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput, final PropertiesPairStreamSupplier provider) { + log.info("Calculate linear mapping from {} to {}.", requiredInput, producedOutput); + + assert requiredInput.getProperties().size() == 1; + assert producedOutput.getProperties().size() == 1; + + final SimpleRegression regression = new SimpleRegression(true); + + provider.get() + .forEach(coordinate -> { + log.info("Mapping - ({}) to ({}).", coordinate.getFirst(), coordinate.getSecond()); + regression.addData(coordinate.getFirst().get(0), coordinate.getSecond().get(0)); + }); + + return new LinearFunction(configuration, LinearFunction.toParameters(regression), requiredInput, actualInput, producedOutput); + } + + @Override + protected PartialSurrogateFunction restoreRegression(final PartialFunctionConfiguration configuration, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput) { + return new LinearFunction(configuration, configuration.getState(), requiredInput, actualInput, producedOutput); + } +} diff --git a/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/quadratic/SimpleQuadraticFunction.java b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/quadratic/SimpleQuadraticFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..455e4598398b28b05d56dfed8c074c8236769d96 --- /dev/null +++ b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/quadratic/SimpleQuadraticFunction.java @@ -0,0 +1,85 @@ +package de.evoal.surrogate.simple.quadratic; + +import java.util.LinkedList; +import java.util.List; +import java.util.function.Function; + +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.function.AbstractPartialSurrogateFunction; +import de.evoal.core.api.properties.Properties; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.math3.stat.regression.SimpleRegression; + +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.PropertySpecification; + +@Slf4j +public class SimpleQuadraticFunction extends AbstractPartialSurrogateFunction { + /** + * Name of the intercept parameter. + */ + private static final String INTERCEPT_PARAMETER_NAME = "intercept"; + + /** + * Name of the r square parameter. + */ + private static final String R_SQUARE_PARAMETER_NAME = "r²"; + + /** + * Name of the slope parameter. + */ + private static final String SLOPE_PARAMETER_NAME = "slope"; + + /** + * Turns an Apache simple regression into a parameter set. + */ + public static List<Parameter> toParameters(final SimpleRegression regression) { + final List<Parameter> result = new LinkedList<>(); + + addParameter(INTERCEPT_PARAMETER_NAME, regression.getIntercept(), result); + addParameter(SLOPE_PARAMETER_NAME, regression.getSlope(), result); + addParameter(R_SQUARE_PARAMETER_NAME, regression.getRSquare(), result); + + return result; + } + + /** + * Actual function for prediction. + */ + private final Function<Properties, Double> regression; + + public SimpleQuadraticFunction(final PartialFunctionConfiguration configuration, final List<Parameter> functionParameters, final PropertiesSpecification input, final PropertiesSpecification actualInput, final PropertiesSpecification output) { + super(configuration, functionParameters, input, output); + + final double slope = getSlope(); + final double intercept = getIntercept(); + + log.info("Using quadratic regression f(x) = {} * x^2 + {}.", slope, intercept); + + final PropertySpecification inputProperty = input.getProperties().get(0); + final int propertyIndex = actualInput.indexOf(inputProperty); + + this.regression = vector -> intercept + slope * vector.get(propertyIndex); + } + + public double [] apply(final Properties input) { + return new double [] {regression.apply(input)}; + } + + private double getIntercept() { + return (double)getParameters().stream() + .filter(p -> INTERCEPT_PARAMETER_NAME.equals(p.getName())) + .findFirst() + .get() + .getValue(); + } + + private double getSlope() { + return (double)getParameters().stream() + .filter(p -> SLOPE_PARAMETER_NAME.equals(p.getName())) + .findFirst() + .get() + .getValue(); + } +} diff --git a/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/quadratic/SimpleQuadraticFunctionFactory.java b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/quadratic/SimpleQuadraticFunctionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..63e673d0b05e98b4964bd0cef592293fa570dbd7 --- /dev/null +++ b/src/core/de.evoal.surrogate.simple/src/main/java/de/evoal/surrogate/simple/quadratic/SimpleQuadraticFunctionFactory.java @@ -0,0 +1,46 @@ +package de.evoal.surrogate.simple.quadratic; + +import java.util.List; + +import de.evoal.core.api.properties.stream.PropertiesPairStreamSupplier; +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.function.AbstractPartialSurrogateFunctionFactory; +import de.evoal.surrogate.api.function.PartialSurrogateFunction; +import de.evoal.surrogate.simple.linear.LinearFunction; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.math3.stat.regression.SimpleRegression; + +import de.evoal.core.api.properties.PropertiesSpecification; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; + +@Dependent +@Named("simple-quadratic-regression") +@Slf4j +public final class SimpleQuadraticFunctionFactory extends AbstractPartialSurrogateFunctionFactory { + + @Override + public PartialSurrogateFunction calculateRegression(final PartialFunctionConfiguration configuration, final List<Parameter> parameters, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput, final PropertiesPairStreamSupplier provider) { + log.info("Calculate linear mapping from {} to {}.", requiredInput, producedOutput); + + assert requiredInput.getProperties().size() == 1; + assert producedOutput.getProperties().size() == 1; + + final SimpleRegression regression = new SimpleRegression(true); + + provider.get() + .forEach(coordinate -> { + log.info("Mapping - ({}) to ({}).", coordinate.getFirst(), coordinate.getSecond()); + regression.addData(Math.pow(coordinate.getFirst().get(0), 2), coordinate.getSecond().get(0)); + }); + + return new SimpleQuadraticFunction(configuration, LinearFunction.toParameters(regression), requiredInput, actualInput, producedOutput); + } + + @Override + protected PartialSurrogateFunction restoreRegression(final PartialFunctionConfiguration configuration, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput) { + return new SimpleQuadraticFunction(configuration, configuration.getState(), requiredInput, actualInput, producedOutput); + } +} diff --git a/src/core/de.evoal.surrogate.simple/src/main/java/module-info.java b/src/core/de.evoal.surrogate.simple/src/main/java/module-info.java index 6b1b1700a4b16236c31bcf6ae4eba99c53c37bb5..687edd5606914791fc8a1505ed37ba44cca7b01c 100644 --- a/src/core/de.evoal.surrogate.simple/src/main/java/module-info.java +++ b/src/core/de.evoal.surrogate.simple/src/main/java/module-info.java @@ -1,2 +1,9 @@ -module $MODULE_NAME$ { +module de.evoal.surrogate.simple { + requires commons.math3; + requires jakarta.enterprise.cdi.api; + requires lombok; + requires org.slf4j; + + requires de.evoal.core.api; + requires de.evoal.surrogate.api; } \ No newline at end of file diff --git a/src/core/de.evoal.surrogate.svr/pom.xml b/src/core/de.evoal.surrogate.svr/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..223ec3187e17099b33c2aba693f9f5425620a349 --- /dev/null +++ b/src/core/de.evoal.surrogate.svr/pom.xml @@ -0,0 +1,72 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>de.evoal.core</groupId> + <artifactId>releng.parent</artifactId> + <version>0.9.0-SNAPSHOT</version> + + <relativePath>../de.evoal.core.releng.parent</relativePath> + </parent> + + <artifactId>surrogate.svr</artifactId> + <name>EvoAl - Surrogate - SVR</name> + + <dependencies> + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>core.api</artifactId> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>${project.groupId}</groupId> + <artifactId>surrogate.api</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>com.github.haifengl</groupId> + <artifactId>smile-core</artifactId> + <version>${smile.version}</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>copy-dependencies</goal> + </goals> + <configuration> + <outputDirectory>${project.build.directory}/${project.artifactId}-dependencies</outputDirectory> + <includeScope>runtime</includeScope> + <excludeScope>provided</excludeScope> + <excludeTransitive>true</excludeTransitive> + </configuration> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifest> + <addClasspath>true</addClasspath> + <classpathPrefix>${project.artifactId}-dependencies/</classpathPrefix> + </manifest> + </archive> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/GaussianKernelSVRFunctionFactory.java b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/GaussianKernelSVRFunctionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..b58d9b6e23d2cf0e4cd990b68308f78d3fa36dd4 --- /dev/null +++ b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/GaussianKernelSVRFunctionFactory.java @@ -0,0 +1,46 @@ +package de.evoal.surrogate.svr; + +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.stream.PropertiesPairStreamSupplier; +import de.evoal.core.api.utils.Requirements; +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.function.AbstractPartialSurrogateFunctionFactory; +import de.evoal.surrogate.api.function.PartialSurrogateFunction; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import smile.math.kernel.MercerKernel; +import smile.regression.KernelMachine; +import smile.regression.SVR; + +import javax.enterprise.context.Dependent; +import javax.inject.Named; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Dependent +@Named("gaussian-svr") +@Slf4j +public class GaussianKernelSVRFunctionFactory extends KernelBasedSVRFunctionFactory { + public GaussianKernelSVRFunctionFactory() { + super(KernelHelper::toGaussianKernel); + } + + @Override + protected PartialSurrogateFunction restoreRegression(final PartialFunctionConfiguration configuration, final PropertiesSpecification actualInput, final PropertiesSpecification requiredInput, final PropertiesSpecification producedOutput) { + final KernelMachine<double []> regression = KernelHelper.fromParameters(configuration.getParameters(), configuration.getState()); + + final double margin = configuration.getParameters() + .stream() + .filter(p -> KernelHelper.SOFT_MARGIN_PARAMETER.equals(p.getName())) + .map(Parameter::getValue) + .map(Double.class::cast) + .findFirst() + .orElse(0.1); + + return new KernelBasedSVRFunction(configuration, regression, requiredInput, actualInput, producedOutput, margin); + } +} diff --git a/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/KernelBasedSVRFunction.java b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/KernelBasedSVRFunction.java new file mode 100644 index 0000000000000000000000000000000000000000..877a5391a63c4a2b30023c2a9016abf0e7fcaff8 --- /dev/null +++ b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/KernelBasedSVRFunction.java @@ -0,0 +1,51 @@ +package de.evoal.surrogate.svr; + +import de.evoal.core.api.properties.Properties; +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.function.AbstractPartialSurrogateFunction; + +import smile.regression.KernelMachine; + +public class KernelBasedSVRFunction extends AbstractPartialSurrogateFunction { + + /** + * Indices of input data + */ + private final int[] indices; + + /** + * Actual SVR + */ + private final KernelMachine<double []> regression; + + private final double gamma; + + public KernelBasedSVRFunction(final PartialFunctionConfiguration configuration, final KernelMachine<double []> regression, final PropertiesSpecification input, final PropertiesSpecification actualInput, final PropertiesSpecification output, final double gamma) { + super(configuration, KernelHelper.toParameters(regression), input, output); + + this.indices = input.getProperties().stream().mapToInt(p -> actualInput.indexOf(p)).toArray(); + + this.regression = regression; + this.gamma = gamma; + } + + @Override + public double [] apply(final Properties input) { + final double [] data = new double[indices.length]; + + for(int i = 0; i < data.length; ++i) { + data[i] = input.get(indices[i]); + } + + return new double [] {regression.predict(data)}; + } + + public KernelMachine<double []> getRegression() { + return regression; + } + + public double getGamma() { + return gamma; + } +} diff --git a/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/KernelBasedSVRFunctionFactory.java b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/KernelBasedSVRFunctionFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..c091422329ffcf18bf23c1f3133d1e8f7cd09c44 --- /dev/null +++ b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/KernelBasedSVRFunctionFactory.java @@ -0,0 +1,64 @@ +package de.evoal.surrogate.svr; + +import de.evoal.core.api.properties.PropertiesSpecification; +import de.evoal.core.api.properties.stream.PropertiesPairStreamSupplier; +import de.evoal.core.api.utils.Requirements; +import de.evoal.surrogate.api.configuration.Parameter; +import de.evoal.surrogate.api.configuration.PartialFunctionConfiguration; +import de.evoal.surrogate.api.function.AbstractPartialSurrogateFunctionFactory; +import de.evoal.surrogate.api.function.PartialSurrogateFunction; +import lombok.extern.slf4j.Slf4j; +import smile.math.kernel.MercerKernel; +import smile.regression.KernelMachine; +import smile.regression.SVR; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Slf4j +public abstract class KernelBasedSVRFunctionFactory extends AbstractPartialSurrogateFunctionFactory { + /** + * Function to create the SVR kernel. + */ + private final Function<Map<String, Object>, MercerKernel<double[]>> toKernel; + + public KernelBasedSVRFunctionFactory(final Function<Map<String, Object>, MercerKernel<double []>> toKernel) { + this.toKernel = toKernel; + } + + @Override + protected PartialSurrogateFunction calculateRegression(final PartialFunctionConfiguration configuration, List<Parameter> parameters, PropertiesSpecification actualInput, PropertiesSpecification requiredInput, PropertiesSpecification producedOutput, PropertiesPairStreamSupplier provider) { + log.info("Calculate SVR surrogate from {} to {}.", requiredInput, producedOutput); + + Requirements.requireSizeGreaterThean(requiredInput.getProperties(), 0); + Requirements.requireSize(producedOutput.getProperties(), 1); + + final List<double []> sources = new ArrayList<>(); + final List<Double> targets = new ArrayList<>(); + + provider.get() + .forEach(p -> { + sources.add(p.getFirst().getValues()); + targets.add(p.getSecond().get(0)); + }); + + log.info("Using {} points for regression.", sources.size()); + + double [][] sourceArray = sources.toArray(new double [][] {}); + double [] targetArray = targets.stream().mapToDouble(Double.class::cast).toArray(); + + final Map<String, Object> params = parameters.stream() + .collect(Collectors.toMap(Parameter::getName, Parameter::getValue)); + + final double epsilon = (double)params.get(KernelHelper.EPSILON_PARAMETER); + final double margin = (double)params.get(KernelHelper.SOFT_MARGIN_PARAMETER); + final double tolerance = (double)params.get(KernelHelper.TOLERANCE_PARAMETER); + + final KernelMachine<double []> regression = SVR.fit(sourceArray, targetArray, toKernel.apply(params), epsilon, margin, tolerance); + + return new KernelBasedSVRFunction(configuration, regression, requiredInput, actualInput, producedOutput, margin); + } +} diff --git a/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/KernelHelper.java b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/KernelHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..fc82572f771eeaf60da1a930c9be0a2bb9248e75 --- /dev/null +++ b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/KernelHelper.java @@ -0,0 +1,175 @@ +package de.evoal.surrogate.svr; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import de.evoal.surrogate.api.configuration.Parameter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import smile.math.kernel.*; +import smile.regression.KernelMachine; +import smile.regression.Regression; + +public final class KernelHelper { + private static final String DEGREE_PARAMETER = "degree"; + + public static final String EPSILON_PARAMETER = "ε"; + + private static final String SCALE_PARAMETER = "scale"; + + private static final String OFFSET_PARAMETER = "offset"; + + private static final String SIGMA_PARAMETER = "σ"; + + public static final String SOFT_MARGIN_PARAMETER = "soft-margin"; + + public static final String TOLERANCE_PARAMETER = "tolerance"; + + private static final String KERNEL_PARAMETER = "kernel"; + + /** + * Logger instance + */ + private final static Logger log = LoggerFactory.getLogger(KernelHelper.class); + + private KernelHelper() { + } + + private static void addParameter(final String name, final Object value, final List<Parameter> parameters) { + final Parameter parameter = Parameter.builder() + .name(name) + .value(value) + .build(); + + parameters.add(parameter); + } + + public static Map<String, Object> toMap(final List<Parameter> parameters) { + return parameters.stream() + .collect(Collectors.toMap(Parameter::getName, Parameter::getValue)); + } + + public static KernelMachine<double []> fromParameters(final List<Parameter> parameters, final List<Parameter> kernelParameters) { + final Map<String, Object> kernelParameterMap = toMap(kernelParameters); + + final MercerKernel<double []> kernel = toKernel(toMap(parameters)); + final double[][] instances =(double[][]) kernelParameterMap.get("instances"); + final double[] weight = (double[]) kernelParameterMap.get("weights"); + final double b = (double)kernelParameterMap.get("intercept"); + + return new KernelMachine<>(kernel, instances, weight, b); + } + + public static MercerKernel<double []> toGaussianKernel(final Map<String, Object> parameters) { + log.info(" Using kernel 'gaussian'"); + final double sigma = (double) parameters.get(SIGMA_PARAMETER); + log.info(" parameter σ={}.", sigma); + + return new GaussianKernel(sigma); + } + + public static MercerKernel<double []> toKernel(final Map<String, Object> parameters) { + log.info(" Using kernel '{}'", parameters.get(KERNEL_PARAMETER)); + + switch((String)parameters.get(KERNEL_PARAMETER)) { + case "gaussian": { + final double sigma = (double)parameters.get(SIGMA_PARAMETER); + + log.info(" parameter σ={}.", sigma); + + return new GaussianKernel(sigma); + } + + case "hellinger": { + return new HellingerKernel(); + } + + case "hyperbolic-tangent": { + final double scale = (double)parameters.get(SCALE_PARAMETER); + final double offset = (double)parameters.get(OFFSET_PARAMETER); + + log.info(" parameter scale is {} and offset is {}.", scale, offset); + + return new HyperbolicTangentKernel(scale, offset); + } + + case "laplacian": { + final double sigma = (double)parameters.get(SIGMA_PARAMETER); + + log.info(" parameter sigma is {}.", sigma); + + return new LaplacianKernel(sigma); + } + + case "linear": { + return new LinearKernel(); + } + + case "pearson": { + final double omega = (double)parameters.get("omega"); + final double sigma = (double)parameters.get(SIGMA_PARAMETER); + + log.info(" parameter ω={} and σ={}.", omega, sigma); + + return new PearsonKernel(omega, sigma); + } + + case "polynomial": { + final int degree = (int)parameters.get(DEGREE_PARAMETER); + final double scale = (double)parameters.get(SCALE_PARAMETER); + final double offset = (double)parameters.get(OFFSET_PARAMETER); + + log.info(" parameter degeree is {}, scale is {}, and offset is {}.", degree, scale, offset); + + return new PolynomialKernel(degree, scale, offset); + } + + case "thin-plate-spline": { + final double sigma = (double)parameters.get(SIGMA_PARAMETER); + + log.info(" parameter σ={}.", sigma); + + return new ThinPlateSplineKernel(sigma); + } + } + + + throw new IllegalArgumentException("The kernel " + parameters.get(KERNEL_PARAMETER) + " is not supported."); + } + + public static List<Parameter> toParameters(final Regression<double[]> regression) { + final KernelMachine<double []> machine = (KernelMachine<double[]>)regression; + + final List<Parameter> result = new LinkedList<>(); + addParameter("weights", machine.weights(), result); + addParameter("intercept", machine.intercept(), result); + addParameter("instances", machine.instances(), result); + + return result; + } + + private void logRegressionParameters(final Regression<double[]> c) { + try { + final Field bField = smile.base.svm.KernelMachine.class.getDeclaredField("b"); + final Field wField = smile.base.svm.KernelMachine.class.getDeclaredField("w"); + + bField.setAccessible(true); + wField.setAccessible(true); + + final double b = bField.getDouble(c); + final double [] w = (double [])wField.get(c); + + log.info("Parameters of kernel are b = {} and w = {}.", b, Arrays.toString(w)); + } catch(final NoSuchFieldException e) { + log.error("Cannot determine the model parameters: Unable to read model of SVR.", e); + } catch (final IllegalArgumentException e) { + log.error("Cannot determine the model parameters: ", e); + } catch (IllegalAccessException e) { + log.error("Cannot determine the model parameters: ", e); + } + }} diff --git a/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/ReflectionHelper.java b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/ReflectionHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..e22a780712fa25a69c39f24e64e53e604b54deee --- /dev/null +++ b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/ReflectionHelper.java @@ -0,0 +1,34 @@ +package de.evoal.surrogate.svr; + +import java.lang.reflect.Field; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ReflectionHelper { + /** + * Logger instance + */ + private final static Logger log = LoggerFactory.getLogger(ReflectionHelper.class); + + private ReflectionHelper() { + } + + public static Object getField(final Object object, final String name) { + try { + final Field field = object.getClass().getDeclaredField(name); + + field.setAccessible(true); + + return field.get(object); + } catch(final NoSuchFieldException e) { + log.error("Cannot determine the model parameters: Unable to read model of SVR.", e); + } catch (final IllegalArgumentException e) { + log.error("Cannot determine the model parameters: ", e); + } catch (IllegalAccessException e) { + log.error("Cannot determine the model parameters: ", e); + } + + throw new IllegalStateException(); + } +} diff --git a/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/package-info.java b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..522538e6a73be4fbdc9624024a5040fc6310ea6d --- /dev/null +++ b/src/core/de.evoal.surrogate.svr/src/main/java/de/evoal/surrogate/svr/package-info.java @@ -0,0 +1,4 @@ +/** + * Package for a kernel-based SVR using smile. + */ +package de.evoal.surrogate.svr; \ No newline at end of file diff --git a/src/core/de.evoal.surrogate.svr/src/main/java/module-info.java b/src/core/de.evoal.surrogate.svr/src/main/java/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..f395b1f9ff1738ef791fe5b0e700cc53b08d0364 --- /dev/null +++ b/src/core/de.evoal.surrogate.svr/src/main/java/module-info.java @@ -0,0 +1,14 @@ +module de.evoal.surrogate.svr { + requires commons.math3; + requires jakarta.enterprise.cdi.api; + requires lombok; + requires org.slf4j; + + requires de.evoal.core.api; + requires de.evoal.surrogate.api; + requires smile.core; + requires smile.math; + requires jakarta.inject.api; + + opens de.evoal.surrogate.svr; +} \ No newline at end of file