diff --git a/.gitignore b/.gitignore index 6845b5ce..f229d2f0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ bin build +# Temporary files created when working on Linux +*~ + # Eclipse files are created with `gradlew eclipse` .classpath .project diff --git a/opt4j-optimizers/build.gradle b/opt4j-optimizers/build.gradle index d159236d..c7090d01 100644 --- a/opt4j-optimizers/build.gradle +++ b/opt4j-optimizers/build.gradle @@ -1,4 +1,7 @@ dependencies { compile project(':opt4j-core') compile project(':opt4j-operators') + + testCompile group: 'junit', name: 'junit', version: '[4.0,)' + testCompile group: 'org.mockito', name: 'mockito-all', version: '1.9.5' } \ No newline at end of file diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerDefaultModule.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerDefaultModule.java new file mode 100644 index 00000000..1392575c --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerDefaultModule.java @@ -0,0 +1,18 @@ +package org.opt4j.optimizers.ea; + +import org.opt4j.core.config.annotations.Name; + +/** + * Binds the {@link CouplerDefault} as the {@link Coupler}. + * + * @author Fedor Smirnov + * + */ +@Name("Default") +public class CouplerDefaultModule extends CouplerModule { + + @Override + protected void config() { + bindCoupler(CouplerDefault.class); + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerModule.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerModule.java new file mode 100644 index 00000000..43a4a361 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerModule.java @@ -0,0 +1,29 @@ +package org.opt4j.optimizers.ea; + +import org.opt4j.core.config.Icons; +import org.opt4j.core.config.annotations.Category; +import org.opt4j.core.config.annotations.Icon; +import org.opt4j.core.config.annotations.Parent; +import org.opt4j.core.start.Opt4JModule; + +/** + * Abstract module class for the {@link Coupler}. + * + * @author Fedor Smirnov + * + */ +@Icon(Icons.SELECTOR) +@Category +@Parent(EvolutionaryAlgorithmModule.class) +public abstract class CouplerModule extends Opt4JModule { + + /** + * Binds the given {@link Coupler}. + * + * @param coupler + * the {@link Coupler} to bind + */ + protected void bindCoupler(Class coupler) { + bind(Coupler.class).to(coupler).in(SINGLETON); + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerRandomModule.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerRandomModule.java new file mode 100644 index 00000000..df8b5a0f --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerRandomModule.java @@ -0,0 +1,18 @@ +package org.opt4j.optimizers.ea; + +import org.opt4j.core.config.annotations.Name; + +/** + * Binds the {@link CouplerRandom} as the {@link Coupler}. + * + * @author Fedor Smirnov + * + */ +@Name("Random") +public class CouplerRandomModule extends CouplerModule { + + @Override + protected void config() { + bindCoupler(CouplerRandom.class); + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerUniqueModule.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerUniqueModule.java new file mode 100644 index 00000000..0a935134 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/CouplerUniqueModule.java @@ -0,0 +1,18 @@ +package org.opt4j.optimizers.ea; + +import org.opt4j.core.config.annotations.Name; + +/** + * Binds the {@link CouplerUnique} as the {@link Coupler}. + * + * @author Fedor Smirnov + * + */ +@Name("Unique") +public class CouplerUniqueModule extends CouplerModule { + + @Override + protected void config() { + bindCoupler(CouplerUnique.class); + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/EvolutionaryAlgorithmModule.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/EvolutionaryAlgorithmModule.java index 9a58cf18..3172fb5e 100644 --- a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/EvolutionaryAlgorithmModule.java +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/EvolutionaryAlgorithmModule.java @@ -19,7 +19,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *******************************************************************************/ - package org.opt4j.optimizers.ea; @@ -47,19 +46,19 @@ public class EvolutionaryAlgorithmModule extends OptimizerModule { protected int generations = 1000; @Constant(value = "alpha", namespace = EvolutionaryAlgorithm.class) - @Info("The size of the population.") + @Info("The size of the population α.") @Order(1) - protected int alpha = 100; + protected int populationSize = 100; @Constant(value = "mu", namespace = EvolutionaryAlgorithm.class) - @Info("The number of parents per generation.") + @Info("The number of parents per generation μ.") @Order(2) - protected int mu = 25; + protected int parentsPerGeneration = 25; @Constant(value = "lambda", namespace = EvolutionaryAlgorithm.class) - @Info("The number of offspring per generation.") + @Info("The number of offsprings per generation λ.") @Order(3) - protected int lambda = 25; + protected int offspringsPerGeneration = 25; @Info("Performs a crossover operation with this given rate.") @Order(4) @@ -86,28 +85,25 @@ public enum CrossoverRateType { /** * Returns the population size {@code alpha}. * - * @see #setAlpha * @return the population size */ - public int getAlpha() { - return alpha; + public int getPopulationSize() { + return populationSize; } /** * Sets the population size {@code alpha}. * - * @see #getAlpha * @param alpha * the population size to set */ - public void setAlpha(int alpha) { - this.alpha = alpha; + public void setPopulationSize(int alpha) { + this.populationSize = alpha; } /** * Returns the number of generations. * - * @see #setGenerations * @return the number of generations */ public int getGenerations() { @@ -128,49 +124,44 @@ public void setGenerations(int generations) { /** * Returns the number of children {@code lambda}. * - * @see #setLambda * @return the number of children */ - public int getLambda() { - return lambda; + public int getOffspringsPerGeneration() { + return offspringsPerGeneration; } /** * Sets the number of children {@code lambda}. * - * @see #getLambda * @param lambda * the number of children */ - public void setLambda(int lambda) { - this.lambda = lambda; + public void setOffspringsPerGeneration(int lambda) { + this.offspringsPerGeneration = lambda; } /** * Returns the number of parents {@code mu}. * - * @see #setMu * @return the number of parents */ - public int getMu() { - return mu; + public int getParentsPerGeneration() { + return parentsPerGeneration; } /** * Sets the number of parents {@code mu}. * - * @see #getMu * @param mu * the number of parents */ - public void setMu(int mu) { - this.mu = mu; + public void setParentsPerGeneration(int mu) { + this.parentsPerGeneration = mu; } /** * Returns the type of crossover rate that is used. * - * @see #setCrossoverRateType * @return the crossoverRateType */ public CrossoverRateType getCrossoverRateType() { @@ -180,7 +171,6 @@ public CrossoverRateType getCrossoverRateType() { /** * Sets the type of crossover rate to use. * - * @see #getCrossoverRateType * @param crossoverRateType * the crossoverRateType to set */ @@ -191,7 +181,6 @@ public void setCrossoverRateType(CrossoverRateType crossoverRateType) { /** * Returns the used crossover rate. * - * @see #setCrossoverRate * @return the crossoverRate */ public double getCrossoverRate() { @@ -201,7 +190,6 @@ public double getCrossoverRate() { /** * Sets the crossover rate. * - * @see #getCrossoverRate * @param crossoverRate * the crossoverRate to set */ @@ -216,9 +204,7 @@ public void setCrossoverRate(double crossoverRate) { */ @Override public void config() { - bindIterativeOptimizer(EvolutionaryAlgorithm.class); - bind(CrossoverRate.class).to(ConstantCrossoverRate.class).in(SINGLETON); } } diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/NonDominatedFronts.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/NonDominatedFronts.java new file mode 100644 index 00000000..370b502f --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/NonDominatedFronts.java @@ -0,0 +1,187 @@ +package org.opt4j.optimizers.ea; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.opt4j.core.Individual; +import org.opt4j.core.Objectives; + +/** + * The {@link NonDominatedFronts} sorts each evaluated {@link Individual} into + * fronts based on the number of other individuals it is dominated by. The first + * front consists of points that are not dominated at all and so on. + * + * @author Fedor Smirnov + * + */ +public class NonDominatedFronts { + + protected final List> fronts; + + /** + * Creates the {@link NonDominatedFronts} for the given collection of + * {@link Individual}s. + * + * @param individuals + * the {@link Individual}s that are sorted into non dominated + * fronts + */ + public NonDominatedFronts(Collection individuals) { + this.fronts = generateFronts(individuals); + } + + /** + * Sorts the given {@link Individual}s into non-dominated fronts. + * + * @param individuals + * the collection of {@link Individual}s that shall be sorted + * @return the non-dominated fronts + */ + protected List> generateFronts(Collection individuals) { + List> fronts = new ArrayList>(); + // Assigns an id to each individual that corresponds to its index in an + // array. + Map indexMap = new HashMap(); + int index = 0; + for (Individual individual : individuals) { + indexMap.put(individual, index++); + } + // Initializes a map where an individual is assigned to the individuals + // that it dominates. + Map> dominatedIndividualsMap = new HashMap>(); + // Creates an array where for each individual, the number of individuals + // that dominate it is stored. + int[] dominatingIndividualNumber = new int[individuals.size()]; + for (Individual e : individuals) { + dominatedIndividualsMap.put(e, new ArrayList()); + dominatingIndividualNumber[indexMap.get(e)] = 0; + } + determineDomination(individuals, dominatedIndividualsMap, dominatingIndividualNumber, indexMap); + // The first front consists of individuals that are dominated by zero + // other individuals. + List f1 = new ArrayList(); + for (Individual i : individuals) { + if (dominatingIndividualNumber[indexMap.get(i)] == 0) { + f1.add(i); + } + } + fronts.add(f1); + List currentFront = f1; + // Creates the subsequent fronts. Front f_i is made up by individuals + // that + // are not dominated if all individuals from fronts f_j with j < i are + // removed. + while (!currentFront.isEmpty()) { + List nextFront = getNextFront(currentFront, dominatedIndividualsMap, dominatingIndividualNumber, + indexMap); + if (!nextFront.isEmpty()) + fronts.add(nextFront); + currentFront = nextFront; + } + return fronts; + } + + /** + * Returns the front at the specified index. + * + * @param index + * the specified index + * @return the front at the specified index + */ + public Collection getFrontAtIndex(int index) { + return fronts.get(index); + } + + /** + * Returns the number of non-dominated fronts. + * + * @return the number of non-dominated fronts + */ + public int getFrontNumber() { + return fronts.size(); + } + + /** + * Finds the next non-dominated front by processing the current + * non-dominated front. The {@link Individual}s found therein are removed + * from consideration. The individuals that are then not dominated form the + * next non-dominated front. + * + * @param currentFront + * the list of individuals forming the current non-dominated + * front + * @param dominatedIndividualsMap + * map mapping an individual on the collection of individuals + * that it dominates + * @param dominatingIndividualNumber + * an array where the number of dominating individuals is stored + * for each individual + * @param individual2IndexMap + * a map storing the indices of the individuals used to access + * the dominatingIndividualNumber + * @return the list of individuals forming the next non-dominated front + */ + protected List getNextFront(List currentFront, + Map> dominatedIndividualsMap, int[] dominatingIndividualNumber, + Map individual2IndexMap) { + List nextFront = new ArrayList(); + for (Individual dominant : currentFront) { + for (Individual dominated : dominatedIndividualsMap.get(dominant)) { + dominatingIndividualNumber[individual2IndexMap.get(dominated)]--; + if (dominatingIndividualNumber[individual2IndexMap.get(dominated)] == 0) { + nextFront.add(dominated); + } + } + } + return nextFront; + } + + /** + * Compares all possible {@link Individual} pairs. For each individual, + * stores 1) the number of individuals it is dominated by and 2) the set of + * individuals it dominates. + * + * @param individuals + * a collection of individuals + * @param dominatedIndividualsMap + * A map that is filled during the execution of the method. Each + * individual is mapped onto the set of individuals that are + * dominated by this individual. + * @param dominatingIndividualNumber + * An integer array (initialized with zeros) that is filled + * during the execution of this method. Each individual is + * associated with an entry of this array. The integer therein is + * the number of individuals this individual is dominated by. + * @param individual2IndexMap + * a map mapping each individual onto its index in the + * dominatingIndividualNumber - array + */ + protected void determineDomination(Collection individuals, + Map> dominatedIndividualsMap, int[] dominatingIndividualNumber, + Map individual2IndexMap) { + List individualList = new ArrayList(individuals); + // compare each individual with each other individual + for (int i = 0; i < individualList.size(); i++) { + for (int j = i + 1; j < individualList.size(); j++) { + Individual p = individualList.get(i); + Individual q = individualList.get(j); + Objectives po = p.getObjectives(); + Objectives qo = q.getObjectives(); + if (po.dominates(qo)) { + dominatedIndividualsMap.get(p).add(q); + dominatingIndividualNumber[individual2IndexMap.get(q)]++; + } else if (qo.dominates(po)) { + dominatedIndividualsMap.get(q).add(p); + dominatingIndividualNumber[individual2IndexMap.get(p)]++; + } + // Neither of the two points dominates the other one, so that + // neither the array + // keeping track of the domination number nor the map containing + // the dominating + // individuals has to be adjusted. Nothing is done in this case. + } + } + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/Nsga2.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/Nsga2.java index e643c1b6..f1fc61c7 100644 --- a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/Nsga2.java +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/Nsga2.java @@ -32,7 +32,6 @@ import java.util.Random; import org.opt4j.core.Individual; -import org.opt4j.core.Objectives; import org.opt4j.core.common.archive.FrontDensityIndicator; import org.opt4j.core.common.random.Rand; import org.opt4j.core.start.Constant; @@ -92,7 +91,7 @@ public Collection getParents(int mu, Collection populati List all = new ArrayList(population); List parents = new ArrayList(); - List> fronts = fronts(all); + NonDominatedFronts fronts = new NonDominatedFronts(all); Map rank = getRank(fronts); Map distance = new HashMap(); @@ -110,7 +109,7 @@ public Collection getParents(int mu, Collection populati // distance if (!distance.containsKey(winner)) { - List front = fronts.get(rank.get(winner)); + List front = new ArrayList(fronts.getFrontAtIndex(rank.get(winner))); distance.putAll(indicator.getDensityValues(front)); } @@ -137,10 +136,9 @@ public Collection getParents(int mu, Collection populati public Collection getLames(int size, Collection population) { List lames = new ArrayList(); - List> fronts = fronts(population); - Collections.reverse(fronts); - - for (List front : fronts) { + NonDominatedFronts fronts = new NonDominatedFronts(population); + for (int i = fronts.getFrontNumber() - 1; i >= 0; i--) { + List front = new ArrayList(fronts.getFrontAtIndex(i)); if (lames.size() + front.size() < size) { lames.addAll(front); } else { @@ -164,99 +162,13 @@ public int compare(Individual o1, Individual o2) { * the fronts * @return the ranks */ - protected Map getRank(List> fronts) { + protected Map getRank(NonDominatedFronts fronts) { Map ranks = new HashMap(); - for (int i = 0; i < fronts.size(); i++) { - for (Individual p : fronts.get(i)) { + for (int i = 0; i < fronts.getFrontNumber(); i++) { + for (Individual p : fronts.getFrontAtIndex(i)) { ranks.put(p, i); } } return ranks; } - - /** - * Evaluate the fronts and set the correspondent rank values. - * - * @param individuals - * the individuals - * @return the fronts - */ - public List> fronts(Collection individuals) { - - List population = new ArrayList(individuals); - Map individualIDs = new HashMap(); - for (int i = 0; i < population.size(); i++) { - individualIDs.put(population.get(i), i); - } - - List> fronts = new ArrayList>(); - - Map> dominatedIndividuals = new HashMap>(); - int[] numberOfDominations = new int[population.size()]; - - for (Individual e : population) { - dominatedIndividuals.put(e, new ArrayList()); - numberOfDominations[individualIDs.get(e)] = 0; - } - - frontsResolveDomination(population, numberOfDominations, individualIDs, dominatedIndividuals); - - List f1 = new ArrayList(); - for (Individual i : population) { - if (numberOfDominations[individualIDs.get(i)] == 0) { - f1.add(i); - } - } - fronts.add(f1); - List fi = f1; - while (!fi.isEmpty()) { - List h = new ArrayList(); - for (Individual p : fi) { - for (Individual q : dominatedIndividuals.get(p)) { - numberOfDominations[individualIDs.get(q)]--; - if (numberOfDominations[individualIDs.get(q)] == 0) { - h.add(q); - } - } - } - fronts.add(h); - fi = h; - } - return fronts; - } - - /** - * Helper function for fronts() that determines the number of dominations - * and the dominated individuals for each individual - * - * @param population - * the individuals to consider - * @param numberOfDominations - * the number of dominations - * @param individualID - * helper ID for the individuals - * @param dominatedIndividuals - * the individuals dominated by each individual - */ - protected void frontsResolveDomination(List population, int[] numberOfDominations, - Map individualID, Map> dominatedIndividuals) { - for (int i = 0; i < population.size(); i++) { - for (int j = i + 1; j < population.size(); j++) { - Individual p = population.get(i); - Individual q = population.get(j); - - Objectives po = p.getObjectives(); - Objectives qo = q.getObjectives(); - - if (po.dominates(qo)) { - dominatedIndividuals.get(p).add(q); - numberOfDominations[individualID.get(q)]++; - } else if (qo.dominates(po)) { - dominatedIndividuals.get(q).add(p); - numberOfDominations[individualID.get(p)]++; - } - } - } - } - } diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/SelectorModule.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/SelectorModule.java index 99c06d8a..07c94b3b 100644 --- a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/SelectorModule.java +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/SelectorModule.java @@ -27,7 +27,6 @@ import org.opt4j.core.config.annotations.Category; import org.opt4j.core.config.annotations.Icon; import org.opt4j.core.config.annotations.Parent; -import org.opt4j.core.optimizer.OptimizerModule; import org.opt4j.core.start.Opt4JModule; /** @@ -38,7 +37,7 @@ */ @Icon(Icons.SELECTOR) @Category -@Parent(OptimizerModule.class) +@Parent(EvolutionaryAlgorithmModule.class) public abstract class SelectorModule extends Opt4JModule { /** diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/AdaptiveEpsilon.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/AdaptiveEpsilon.java new file mode 100644 index 00000000..bb28c9f0 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/AdaptiveEpsilon.java @@ -0,0 +1,53 @@ +package org.opt4j.optimizers.ea.aeseh; + +/** + * The {@link AdaptiveEpsilon} contains the information about an ε-value and the + * information that is used by the {@link EpsilonAdaptation} to adapt the + * ε-value. + * + * @author Fedor Smirnov + * + */ +public class AdaptiveEpsilon { + + protected double epsilon; + protected double epsilonDelta; + protected final double epsilonDeltaMax; + protected final double epsilonDeltaMin; + + public AdaptiveEpsilon(double epsilon, double epsilonDelta, double epsilonDeltaMax, double epsilonDeltaMin) { + if (epsilonDelta < epsilonDeltaMin || epsilonDelta > epsilonDeltaMax) { + throw new IllegalArgumentException("The ε-delta value exceeds the given bounds"); + } + this.epsilon = epsilon; + this.epsilonDelta = epsilonDelta; + this.epsilonDeltaMax = epsilonDeltaMax; + this.epsilonDeltaMin = epsilonDeltaMin; + } + + public double getEpsilon() { + return epsilon; + } + + public void setEpsilon(double epsilon) { + this.epsilon = epsilon; + } + + public double getEpsilonDelta() { + return epsilonDelta; + } + + public void setEpsilonDelta(double epsilonDelta) { + double delta = Math.max(epsilonDeltaMin, epsilonDelta); + delta = Math.min(epsilonDeltaMax, delta); + this.epsilonDelta = delta; + } + + public double getEpsilonDeltaMax() { + return epsilonDeltaMax; + } + + public double getEpsilonDeltaMin() { + return epsilonDeltaMin; + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/AeSeHModule.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/AeSeHModule.java new file mode 100644 index 00000000..58ddd692 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/AeSeHModule.java @@ -0,0 +1,46 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.opt4j.core.config.annotations.Citation.PublicationMonth.UNKNOWN; + +import org.opt4j.core.config.annotations.Citation; +import org.opt4j.core.config.annotations.Info; +import org.opt4j.core.config.annotations.Parent; +import org.opt4j.core.optimizer.OptimizerModule; +import org.opt4j.optimizers.ea.Coupler; +import org.opt4j.optimizers.ea.EvolutionaryAlgorithmModule; +import org.opt4j.optimizers.ea.Selector; + +/** + * The {@link AeSeHModule} binds the {@link EpsilonSamplingSelector} as + * {@link Selector} and the {@link EpsilonNeighborhoodCoupler} as + * {@link Coupler} and configures them with the parameters used in the AeSeH + * paper (see the citation for details). Together with the + * {@link EvolutionaryAlgorithmModule}, the {@link AeSeHModule}, hence, provides + * a default configuration for the Epsilon Sampling Epsilon Neighborhood + * Evolutionary Algorithm. + * + * @author Fedor Smirnov + */ +@Info("Multi-objective evolutionary algorithm where the survival selection and the creation of neighborhoods is based on epsilon-dominance. The selection of parents is done within the created neighborhoods.") +@Citation(authors = "Hernán Aguirre, Akira Oyama, and Kiyoshi Tanaka", title = "Adaptive ε-sampling and ε-hood for evolutionary many-objective optimization.", journal = "Evolutionary Multi-Criterion Optimization (EMO)", pageFirst = 322, pageLast = 336, year = 2013, month = UNKNOWN) +@Parent(EvolutionaryAlgorithmModule.class) +public class AeSeHModule extends OptimizerModule { + + @Override + protected void config() { + + bindConstant("epsilonSample", ESamplingSurvivorGeneration.class).to(0.0); + bindConstant("epsilonSampleDelta", ESamplingSurvivorGeneration.class).to(0.005); + bindConstant("epsilonSampleDeltaMax", ESamplingSurvivorGeneration.class).to(0.005); + bindConstant("epsilonSampleDeltaMin", ESamplingSurvivorGeneration.class).to(0.0001); + + bindConstant("epsilonNeighborhood", EpsilonNeighborhoodCoupler.class).to(0.0); + bindConstant("epsilonNeighborhoodDelta", EpsilonNeighborhoodCoupler.class).to(0.005); + bindConstant("epsilonNeighborhoodDeltaMax", EpsilonNeighborhoodCoupler.class).to(0.005); + bindConstant("epsilonNeighborhoodDeltaMin", EpsilonNeighborhoodCoupler.class).to(0.0001); + bindConstant("neighborhoodNumber", EpsilonNeighborhoodCoupler.class).to(5); + + bind(Selector.class).to(EpsilonSamplingSelector.class); + bind(Coupler.class).to(EpsilonNeighborhoodCoupler.class); + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGeneration.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGeneration.java new file mode 100644 index 00000000..abba358e --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGeneration.java @@ -0,0 +1,34 @@ +package org.opt4j.optimizers.ea.aeseh; + +import java.util.Collection; +import java.util.Set; + +import org.opt4j.core.Individual; + +import com.google.inject.ImplementedBy; + +/** + * + * The {@link ESamplingSurvivorGeneration} generates the survivor pool during + * the selection implemented by {@link EpsilonSamplingSelector}. + * + * @author Fedor Smirnov + * + */ +@ImplementedBy(ESamplingSurvivorGenerationBasic.class) +public interface ESamplingSurvivorGeneration { + + /** + * Generates the survivors out of the input collection of {@link Individual}s. + * + * @param population + * the current population (union of the parent- and the + * offspring-sets from the current iteration) + * @param survivorNumber + * the number of survivors to create + * @return the survivors (used as the pool for the parent candidates for the + * next generation) + */ + public Set getSurvivors(Collection population, int survivorNumber); + +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGenerationBasic.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGenerationBasic.java new file mode 100644 index 00000000..c004feae --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGenerationBasic.java @@ -0,0 +1,231 @@ +package org.opt4j.optimizers.ea.aeseh; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.opt4j.core.Individual; +import org.opt4j.core.Objective; +import org.opt4j.core.Objectives; +import org.opt4j.core.start.Constant; +import org.opt4j.optimizers.ea.NonDominatedFronts; + +import com.google.inject.Inject; + +/** + * The {@link ESamplingSurvivorGenerationBasic} implements the basic survivor + * selection used by the Adaptive ε-sampling and ε-hood for evolutionary + * many-objective optimization. + * + * @author Fedor Smirnov + * + */ +public class ESamplingSurvivorGenerationBasic implements ESamplingSurvivorGeneration { + + protected final Random random; + protected final EpsilonMapping epsilonMapping; + protected final EpsilonAdaptation epsilonAdaption; + protected final AdaptiveEpsilon adaptiveEpsilonSampling; + + /** + * Basic constructor. + * + * @param epsilonMapping + * an {@link EpsilonMapping} that is used to enhance the + * {@link Objectives} during the choice of the survivors + * @param epsilonAdaption + * an {@link EpsilonAdaptation} that adjusts the ε valued used + * for the choice of the survivors + * @param random + * the {@link Random} used for the sampling + */ + @Inject + public ESamplingSurvivorGenerationBasic(Random random, EpsilonMapping epsilonMapping, + EpsilonAdaptation epsilonAdaption, + @Constant(value = "epsilonSample", namespace = ESamplingSurvivorGeneration.class) double epsilonSample, + @Constant(value = "epsilonSampleDelta", namespace = ESamplingSurvivorGeneration.class) double epsilonSampleDelta, + @Constant(value = "epsilonSampleDeltaMax", namespace = ESamplingSurvivorGeneration.class) double epsilonSampleDeltaMax, + @Constant(value = "epsilonSampleDeltaMin", namespace = ESamplingSurvivorGeneration.class) double epsilonSampleDeltaMin) { + this.random = random; + this.epsilonMapping = epsilonMapping; + this.epsilonAdaption = epsilonAdaption; + this.adaptiveEpsilonSampling = new AdaptiveEpsilon(epsilonSample, epsilonSampleDelta, epsilonSampleDeltaMax, + epsilonSampleDeltaMin); + } + + @Override + public Set getSurvivors(Collection population, int survivorNumber) { + Set survivors; + // get the non-dominated front and the extreme solutions + NonDominatedFronts fronts = new NonDominatedFronts(population); + Collection paretoSolutions = fronts.getFrontAtIndex(0); + Set extremeIndividuals = getExtremeIndividuals(paretoSolutions); + + if (paretoSolutions.size() > survivorNumber) { + // more non-dominated solutions than survivors => apply ε-sampling + survivors = addNonDominatedSurvivors(extremeIndividuals, paretoSolutions, survivorNumber); + } else { + survivors = addDominatedSurvivors(survivorNumber, fronts); + } + return survivors; + } + + /** + * Creates the survivor pool by adding the ε-sampled individuals to the + * extreme individuals. + * + * @param extremeIndividuals + * the {@link Individual}s with (positively) extreme values for + * their {@link Objective}s + * @param firstFront + * the {@link Individual} that are not dominated at all + * @param survivorNumber + * the desired number of survivors + */ + protected Set addNonDominatedSurvivors(Collection extremeIndividuals, + Collection firstFront, int survivorNumber) { + Set survivors = new HashSet(); + List nonDominatedIndividuals = new ArrayList(firstFront); + // the extreme values always survive + survivors.addAll(extremeIndividuals); + nonDominatedIndividuals.removeAll(extremeIndividuals); + Set epsilonDominantIndividuals = new HashSet(); + Set epsilonDominatedIndividuals = new HashSet(); + applyEpsilonSampling(nonDominatedIndividuals, epsilonDominantIndividuals, epsilonDominatedIndividuals, + adaptiveEpsilonSampling.getEpsilon()); + boolean tooManyEpsilonDominantIndividuals = (extremeIndividuals.size() + + epsilonDominantIndividuals.size()) > survivorNumber; + // adapt the sampling epsilon + boolean epsilonTooBig = !tooManyEpsilonDominantIndividuals; + epsilonAdaption.adaptEpsilon(adaptiveEpsilonSampling, epsilonTooBig); + if (tooManyEpsilonDominantIndividuals) { + // add a random subset of the epsilon dominant individuals + List survivalCandidates = new ArrayList(epsilonDominantIndividuals); + while (survivors.size() < survivorNumber) { + int idx = random.nextInt(survivalCandidates.size()); + Individual survivor = survivalCandidates.get(idx); + survivors.add(survivor); + survivalCandidates.remove(idx); + } + } else { + // add a random subset from the epsilon dominated individuals + survivors.addAll(epsilonDominantIndividuals); + List survivorCandidates = new ArrayList(epsilonDominatedIndividuals); + while (survivors.size() < survivorNumber) { + int idx = random.nextInt(survivorCandidates.size()); + Individual survivor = survivorCandidates.get(idx); + survivorCandidates.remove(idx); + survivors.add(survivor); + } + } + return survivors; + } + + /** + * Applies ε-sampling by dividing the given individuals into the two sets of + * ε-dominant and ε-dominated individuals. + * + * @param firstFront + * the input individuals which constitute the first non-dominated + * front of the current population + * @param epsilonDominantIndividuals + * the set that will be filled with the epsilon-dominant + * individuals + * @param epsilonDominatedIndividuals + * the set that will be filled with epsilon-dominated individuals + * @param samplingEpsilon + * the value used for the epsilon sampling + */ + protected void applyEpsilonSampling(List firstFront, Set epsilonDominantIndividuals, + Set epsilonDominatedIndividuals, double samplingEpsilon) { + // apply epsilon sampling until the individual list is empty + List nonDominatedIndividuals = new ArrayList(firstFront); + Map objectiveAmplitudes = epsilonMapping + .findObjectiveAmplitudes(new HashSet(nonDominatedIndividuals)); + while (!nonDominatedIndividuals.isEmpty()) { + // pick a random individual + Individual epsilonDominant = nonDominatedIndividuals.get(random.nextInt(nonDominatedIndividuals.size())); + Set epsilonDominated = new HashSet(); + nonDominatedIndividuals.remove(epsilonDominant); + Objectives epsilonEnhancedObjectives = epsilonMapping.mapObjectives(epsilonDominant.getObjectives(), + samplingEpsilon, objectiveAmplitudes); + // gather all individuals epsilon dominated by the picked individual + for (int i = 0; i < nonDominatedIndividuals.size(); i++) { + Individual comparisonIndividual = nonDominatedIndividuals.get(i); + if (epsilonEnhancedObjectives.dominates(comparisonIndividual.getObjectives())) { + epsilonDominated.add(comparisonIndividual); + } + } + nonDominatedIndividuals.removeAll(epsilonDominated); + epsilonDominantIndividuals.add(epsilonDominant); + epsilonDominatedIndividuals.addAll(epsilonDominated); + } + } + + /** + * In the case where the first non-dominated front does not suffice to + * create enough survivors, dominated solutions are added to the survivor + * pool. + * + * @param survivorNumber + * the desired number of survivors + * @param fronts + * the non-dominated fronts + */ + protected Set addDominatedSurvivors(int survivorNumber, NonDominatedFronts fronts) { + Set survivors = new HashSet(); + // non-dominated solutions do not suffice to generate the number of + // survivors => add dominated solutions + survivors.addAll(fronts.getFrontAtIndex(0)); + int frontIndex = 1; + // Fill the survivors by iteratively adding the dominated fronts. + while (survivorNumber > (fronts.getFrontAtIndex(frontIndex).size()) + survivors.size()) { + survivors.addAll(fronts.getFrontAtIndex(frontIndex)); + frontIndex++; + } + List currentFront = new ArrayList(fronts.getFrontAtIndex(frontIndex)); + while (survivorNumber > survivors.size()) { + // choose a random survivor from the current front + Individual survivor = currentFront.get(random.nextInt(currentFront.size())); + survivors.add(survivor); + currentFront.remove(survivor); + } + return survivors; + } + + /** + * Returns the {@link Individual}s with the best values for the individual + * {@link Objective}. + * + * @param firstFront + * the list of {@link Individual}s constituting the first + * non-dominated front + * + * @return the set of the extreme individuals + */ + protected Set getExtremeIndividuals(Collection firstFront) { + Map bestIndis = new HashMap(); + Map extremeValues = new HashMap(); + Individual firstIndi = firstFront.iterator().next(); + List objList = new ArrayList(firstIndi.getObjectives().getKeys()); + // iterate the individuals + for (Individual indi : firstFront) { + // iterate the objectives and their values + double[] values = indi.getObjectives().array(); + for (int i = 0; i < objList.size(); i++) { + Objective obj = objList.get(i); + double value = values[i]; + if (!bestIndis.containsKey(obj) || extremeValues.get(obj) > value) { + bestIndis.put(obj, indi); + extremeValues.put(obj, value); + } + } + } + return new HashSet(bestIndis.values()); + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonAdaptation.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonAdaptation.java new file mode 100644 index 00000000..31f07491 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonAdaptation.java @@ -0,0 +1,25 @@ +package org.opt4j.optimizers.ea.aeseh; + +import com.google.inject.ImplementedBy; + +/** + * The {@link EpsilonAdaptation} manages the adaptation of the ε-value stored in + * the {@link AdaptiveEpsilon}. + * + * @author Fedor Smirnov + * + */ +@ImplementedBy(EpsilonAdaptationDelta.class) +public interface EpsilonAdaptation { + + /** + * Adjusts the ε-value according to the given {@link AdaptiveEpsilon}. + * + * @param adaptiveEpsilon + * the {@link AdaptiveEpsilon} contains the ε-value and the + * information about its adaptation + * @param epsilonTooBig + * {@code true} if the current epsilon is too big + */ + public void adaptEpsilon(AdaptiveEpsilon adaptiveEpsilon, boolean epsilonTooBig); +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonAdaptationDelta.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonAdaptationDelta.java new file mode 100644 index 00000000..17ca3dae --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonAdaptationDelta.java @@ -0,0 +1,22 @@ +package org.opt4j.optimizers.ea.aeseh; + +/** + * The {@link EpsilonAdaptationDelta} adapts the ε values exactly as described + * in the paper cited in the {@link AeSeHModule}. + * + * @author Fedor Smirnov + * + */ +public class EpsilonAdaptationDelta implements EpsilonAdaptation { + + @Override + public void adaptEpsilon(AdaptiveEpsilon adaptiveEpsilon, boolean epsilonTooBig) { + if (epsilonTooBig) { + adaptiveEpsilon.setEpsilon(adaptiveEpsilon.getEpsilon() - adaptiveEpsilon.getEpsilonDelta()); + adaptiveEpsilon.setEpsilonDelta(adaptiveEpsilon.getEpsilonDelta() / 2); + } else { + adaptiveEpsilon.setEpsilon(adaptiveEpsilon.getEpsilon() + adaptiveEpsilon.getEpsilonDelta()); + adaptiveEpsilon.setEpsilonDelta(adaptiveEpsilon.getEpsilonDelta() * 2); + } + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonMapping.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonMapping.java new file mode 100644 index 00000000..6d5da100 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonMapping.java @@ -0,0 +1,48 @@ +package org.opt4j.optimizers.ea.aeseh; + +import java.util.Map; +import java.util.Set; + +import org.opt4j.core.Individual; +import org.opt4j.core.Objective; +import org.opt4j.core.Objectives; + +import com.google.inject.ImplementedBy; + +/** + * The {@link EpsilonMapping} implements the ε mapping used by the + * {@link EpsilonSamplingSelector}. + * + * @author Fedor Smirnov + * + */ +@ImplementedBy(EpsilonMappingAdditive.class) +public interface EpsilonMapping { + + /** + * Maps the given {@link Objectives} on the objectives used for the check of the ε + * dominance. + * + * @param original + * the actual objectives of the individual + * @param epsilon + * the ε value + * @param objectiveAmplitudes + * a map containing the amplitude values of the objectives + * @return {@link Objectives} enhanced by the ε value + */ + public Objectives mapObjectives(final Objectives original, double epsilon, + Map objectiveAmplitudes); + + /** + * Creates a map mapping the {@link Objective}s to their amplitudes (difference between + * maximal and minimal value). These extreme values are used during ε mapping to + * scale the delta according to the objective values of the {@link Individual} group + * under consideration. + * + * @param individuals + * @return map mapping each {@link Objective} onto its amplitude + */ + public Map findObjectiveAmplitudes(Set individuals); + +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonMappingAdditive.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonMappingAdditive.java new file mode 100644 index 00000000..96a8fda2 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonMappingAdditive.java @@ -0,0 +1,83 @@ +package org.opt4j.optimizers.ea.aeseh; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.opt4j.core.Individual; +import org.opt4j.core.Objective; +import org.opt4j.core.Objective.Sign; +import org.opt4j.core.Objectives; + +/** + * The {@link EpsilonMappingAdditive} implements the evenly spaced adaptive ε function. + * + * @author Fedor Smirnov + * + */ +public class EpsilonMappingAdditive implements EpsilonMapping { + + /** + * Applies ε mapping by enhancing all of the given {@link Objective}s by the ε + * fraction of the objective amplitude. + * + * @param original + * the {@link Objectives} that are enhanced by this method + * @param epsilon + * the fraction used for the enhancement + * @param objectiveAmplitudes + * the map mapping its objective onto its amplitude + * @return enhanced {@link Objectives} where each {@link Objective} is improved + * by the ε fraction of the objective's amplitude + */ + @Override + public Objectives mapObjectives(Objectives original, double epsilon, Map objectiveAmplitudes) { + Objectives result = new Objectives(); + Iterator iterator = original.getKeys().iterator(); + double[] values = original.array(); + for (int i = 0; i < original.size(); i++) { + Objective obj = iterator.next(); + double value = values[i] * (obj.getSign().equals(Sign.MIN) ? 1 : -1); + if (objectiveAmplitudes.containsKey(obj)) { + // the ε mapping is only applied if the objective is feasible for at least one + // individual + value += epsilon * objectiveAmplitudes.get(obj) * (obj.getSign().equals(Sign.MAX) ? 1 : -1); + } + result.add(obj, value); + } + return result; + } + + @Override + public Map findObjectiveAmplitudes(Set individuals) { + Map maximumMap = new HashMap(); + Map minimumMap = new HashMap(); + Map amplitudeMap = new HashMap(); + for (Individual indi : individuals) { + Objectives objectives = indi.getObjectives(); + Iterator iterator = objectives.getKeys().iterator(); + double[] values = objectives.array(); + for (int i = 0; i < objectives.size(); i++) { + Objective obj = iterator.next(); + if (objectives.get(obj).getValue() == Objective.INFEASIBLE) + continue; + double value = values[i]; + if (!maximumMap.containsKey(obj) || maximumMap.get(obj) < value) { + maximumMap.put(obj, value); + } + if (!minimumMap.containsKey(obj) || minimumMap.get(obj) > value) { + minimumMap.put(obj, value); + } + } + } + for (Entry maxEntry : maximumMap.entrySet()) { + Objective obj = maxEntry.getKey(); + Double maximum = maxEntry.getValue(); + Double minimum = minimumMap.get(obj); + amplitudeMap.put(obj, maximum - minimum); + } + return amplitudeMap; + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonNeighborhoodCoupler.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonNeighborhoodCoupler.java new file mode 100644 index 00000000..f815d3b2 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonNeighborhoodCoupler.java @@ -0,0 +1,146 @@ +package org.opt4j.optimizers.ea.aeseh; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +import org.opt4j.core.Individual; +import org.opt4j.core.Objective; +import org.opt4j.core.Objectives; +import org.opt4j.core.start.Constant; +import org.opt4j.operators.crossover.Pair; +import org.opt4j.optimizers.ea.Coupler; + +import com.google.inject.Inject; + +/** + * The {@link EpsilonNeighborhoodCoupler} implements a parent selection process based on by + * the ε-neighborhood. + * + * @author Fedor Smirnov + * + */ +public class EpsilonNeighborhoodCoupler implements Coupler { + + protected final EpsilonAdaptation epsilonAdaption; + protected final EpsilonMapping epsilonMapping; + protected final Random random; + protected final int plannedNeighborhoodNumber; + protected final AdaptiveEpsilon adaptiveEpsilonNeighborhood; + + /** + * Basic constructor. + * + * @param epsilonMapping + * an {@link EpsilonMapping} that is used to enhance the + * {@link Objectives} during the creation of the neighborhoods + * @param epsilonAdaption + * an {@link EpsilonAdaptation} that adjusts the ε valued used + * for the creation of the neighborhoods + * @param random + * a {@link Random} + * @param plannedNeighborhoodNumber + * A value provided by the user. The ε used for the creation of + * the neighborhoods is adjusted in order to create a number of + * neighborhoods similar to this value. + */ + @Inject + public EpsilonNeighborhoodCoupler(EpsilonMapping epsilonMapping, EpsilonAdaptation epsilonAdaption, Random random, + @Constant(value = "neighborhoodNumber", namespace = EpsilonNeighborhoodCoupler.class) int plannedNeighborhoodNumber, + @Constant(value = "epsilonNeighborhood", namespace = EpsilonNeighborhoodCoupler.class) double epsilonNeighborhood, + @Constant(value = "epsilonNeighborhoodDelta", namespace = EpsilonNeighborhoodCoupler.class) double epsilonNeighborhoodDelta, + @Constant(value = "epsilonNeighborhoodDeltaMax", namespace = EpsilonNeighborhoodCoupler.class) double epsilonNeighborhoodDeltaMax, + @Constant(value = "epsilonNeighborhoodDeltaMin", namespace = EpsilonNeighborhoodCoupler.class) double epsilonNeighborhoodDeltaMin) { + this.epsilonMapping = epsilonMapping; + this.epsilonAdaption = epsilonAdaption; + this.random = random; + this.plannedNeighborhoodNumber = plannedNeighborhoodNumber; + this.adaptiveEpsilonNeighborhood = new AdaptiveEpsilon(epsilonNeighborhood, epsilonNeighborhoodDelta, + epsilonNeighborhoodDeltaMax, epsilonNeighborhoodDeltaMin); + } + + /** + * Generates parent couples. Distributes the parent {@link Individual}s onto + * neighborhoods. Both parents of a couple are picked from the same + * neighborhood. Uses a {@link NeighborhoodSchedulerRoundRobin} to arbitrate + * the neighborhoods from where the parent couples are picked. + * + * @param size + * the number of couples that is generated + * @param survivors + * the {@link Individual}s that can be used as parents + * @return a collection of {@link Individual} pairs, that will be used to + * generate the next generation of individuals + * + */ + @Override + public Collection> getCouples(int size, List survivors) { + Collection> result = new HashSet>(); + List> neighborhoods = createNeighborhoods(survivors); + NeighborhoodSchedulerRoundRobin scheduler = new NeighborhoodSchedulerRoundRobin(neighborhoods); + for (int i = 0; i < size; i++) { + result.add(pickCouple(scheduler.next())); + } + return result; + } + + /** + * Picks a couple of parents from the given neighborhood. Here, we just pick + * two random individuals. + * + * @param neighborhood + * the set of similar {@link Individual}s from where the parents + * are picked + * @return the pair that was picked as parents for a crossover + */ + protected Pair pickCouple(Set neighborhood) { + if (neighborhood.size() == 1) { + Individual hermit = neighborhood.iterator().next(); + return new Pair(hermit, hermit); + } + List individualList = new ArrayList(neighborhood); + Individual first = individualList.remove(random.nextInt(individualList.size())); + Individual second = individualList.remove(random.nextInt(individualList.size())); + return new Pair(first, second); + } + + /** + * Applies the epsilon neighborhood creation. + * + * @param survivorPool + * a list of {@link Individual}s that can be used as parents + * @return a list of individual sets. Each set is considered as a + * neighborhood. + */ + protected List> createNeighborhoods(List survivorPool) { + List> neighborhoods = new ArrayList>(); + Map objectiveAmplitudes = epsilonMapping + .findObjectiveAmplitudes(new HashSet(survivorPool)); + List survivors = new ArrayList(survivorPool); + while (!survivors.isEmpty()) { + // pick a random individual + int idx = random.nextInt(survivors.size()); + Individual reference = survivors.remove(idx); + Set neighborhood = new HashSet(); + Objectives epsilonEnhancedObjectives = epsilonMapping.mapObjectives(reference.getObjectives(), + adaptiveEpsilonNeighborhood.getEpsilon(), objectiveAmplitudes); + // put the individuals epsilon-dominated by the reference into its + // neighborhood + for (Individual candidate : survivors) { + if (epsilonEnhancedObjectives.dominates(candidate.getObjectives())) + neighborhood.add(candidate); + } + survivors.removeAll(neighborhood); + neighborhood.add(reference); + neighborhoods.add(neighborhood); + } + // adapt the epsilon + boolean epsilonTooBig = neighborhoods.size() < plannedNeighborhoodNumber; + epsilonAdaption.adaptEpsilon(adaptiveEpsilonNeighborhood, epsilonTooBig); + return neighborhoods; + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonNeighborhoodCouplerModule.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonNeighborhoodCouplerModule.java new file mode 100644 index 00000000..388b9f15 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonNeighborhoodCouplerModule.java @@ -0,0 +1,83 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.opt4j.core.config.annotations.Citation.PublicationMonth.UNKNOWN; + +import org.opt4j.core.config.annotations.Citation; +import org.opt4j.core.config.annotations.Info; +import org.opt4j.core.config.annotations.Name; +import org.opt4j.core.start.Constant; +import org.opt4j.optimizers.ea.Coupler; +import org.opt4j.optimizers.ea.CouplerModule; + +/** + * Binds the {@link EpsilonNeighborhoodCoupler} as the {@link Coupler}. + * + * @author Fedor Smirnov + * + */ +@Name("Epsilon Neighborhood") +@Citation(authors = "Hernán Aguirre, Akira Oyama, and Kiyoshi Tanaka", title = "Adaptive ε-sampling and ε-hood for evolutionary many-objective optimization.", journal = "Evolutionary Multi-Criterion Optimization (EMO)", pageFirst = 322, pageLast = 336, year = 2013, month = UNKNOWN) +public class EpsilonNeighborhoodCouplerModule extends CouplerModule { + + @Info("The reference value for the number of neighborhoods. The epsilon_h value is adapted to create a similar number of neighborhoods.") + @Constant(value = "neighborhoodNumber", namespace = EpsilonNeighborhoodCoupler.class) + protected int plannedNeighborhoodNumber = 5; + @Info("The start value used for the epsilon value which is applied during the neighborhood creation.") + @Constant(value = "epsilonNeighborhood", namespace = EpsilonNeighborhoodCoupler.class) + protected double epsilonNeighborhood = 0.0; + @Info("The start value used for the adaptation of the epsilon value which is applied during the neighborhood creation.") + @Constant(value = "epsilonNeighborhoodDelta", namespace = EpsilonNeighborhoodCoupler.class) + protected double epsilonNeighborhoodDelta = 0.005; + @Info("The maximal value used for the epsilon value which is applied during the neighborhood creation.") + @Constant(value = "epsilonNeighborhoodDeltaMax", namespace = EpsilonNeighborhoodCoupler.class) + protected double epsilonNeighborhoodDeltaMax = 0.005; + @Info("The minimal value used for the epsilon value which is applied during the neighborhood creation.") + @Constant(value = "epsilonNeighborhoodDeltaMin", namespace = EpsilonNeighborhoodCoupler.class) + protected double epsilonNeighborhoodDeltaMin = 0.0001; + + @Override + protected void config() { + bindCoupler(EpsilonNeighborhoodCoupler.class); + } + + public int getPlannedNeighborhoodNumber() { + return plannedNeighborhoodNumber; + } + + public void setPlannedNeighborhoodNumber(int plannedNeighborhoodNumber) { + this.plannedNeighborhoodNumber = plannedNeighborhoodNumber; + } + + public double getEpsilonNeighborhood() { + return epsilonNeighborhood; + } + + public void setEpsilonNeighborhood(double epsilonNeighborhood) { + this.epsilonNeighborhood = epsilonNeighborhood; + } + + public double getEpsilonNeighborhoodDelta() { + return epsilonNeighborhoodDelta; + } + + public void setEpsilonNeighborhoodDelta(double epsilonNeighborhoodDelta) { + this.epsilonNeighborhoodDelta = epsilonNeighborhoodDelta; + } + + public double getEpsilonNeighborhoodDeltaMax() { + return epsilonNeighborhoodDeltaMax; + } + + public void setEpsilonNeighborhoodDeltaMax(double epsilonNeighborhoodDeltaMax) { + this.epsilonNeighborhoodDeltaMax = epsilonNeighborhoodDeltaMax; + } + + public double getEpsilonNeighborhoodDeltaMin() { + return epsilonNeighborhoodDeltaMin; + } + + public void setEpsilonNeighborhoodDeltaMin(double epsilonNeighborhoodDeltaMin) { + this.epsilonNeighborhoodDeltaMin = epsilonNeighborhoodDeltaMin; + } + +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonSamplingSelector.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonSamplingSelector.java new file mode 100644 index 00000000..b9b314d2 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonSamplingSelector.java @@ -0,0 +1,57 @@ +package org.opt4j.optimizers.ea.aeseh; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import org.opt4j.core.Individual; +import org.opt4j.optimizers.ea.Selector; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * The {@link EpsilonSamplingSelector} implements a selection process based on the + * ε-sampling. + * + * @author Fedor Smirnov + * + */ +@Singleton +public class EpsilonSamplingSelector implements Selector { + + protected final ESamplingSurvivorGeneration survivorGeneration; + + /** + * Basic constructor + * + * @param survivorGeneration + * the {@link ESamplingSurvivorGeneration} used for picking the + * {@link Individual}s that will survive and act as potential parents + */ + @Inject + public EpsilonSamplingSelector(ESamplingSurvivorGeneration survivorGeneration) { + this.survivorGeneration = survivorGeneration; + } + + @Override + public Collection getParents(int mu, Collection population) { + // Does nothing. In the AeSeH algorithm, all survivors are considered as possible + // parents. + return population; + } + + @Override + public Collection getLames(int lambda, Collection population) { + int survivorNumber = population.size() - lambda; + Set survivors = survivorGeneration.getSurvivors(population, survivorNumber); + Set lames = new HashSet(population); + lames.removeAll(survivors); + return lames; + } + + @Override + public void init(int maxsize) { + // Does nothing. + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonSamplingSelectorModule.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonSamplingSelectorModule.java new file mode 100644 index 00000000..fed1d6bf --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/EpsilonSamplingSelectorModule.java @@ -0,0 +1,73 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.opt4j.core.config.annotations.Citation.PublicationMonth.UNKNOWN; + +import org.opt4j.core.config.annotations.Citation; +import org.opt4j.core.config.annotations.Info; +import org.opt4j.core.config.annotations.Name; +import org.opt4j.core.start.Constant; +import org.opt4j.optimizers.ea.Selector; +import org.opt4j.optimizers.ea.SelectorModule; + +/** + * Binds the {@link EpsilonSamplingSelector} as {@link Selector}. + * + * + * @author Fedor Smirnov + * + */ +@Name("Epsilon Sampling") +@Citation(authors = "Hernán Aguirre, Akira Oyama, and Kiyoshi Tanaka", title = "Adaptive ε-sampling and ε-hood for evolutionary many-objective optimization.", journal = "Evolutionary Multi-Criterion Optimization (EMO)", pageFirst = 322, pageLast = 336, year = 2013, month = UNKNOWN) +public class EpsilonSamplingSelectorModule extends SelectorModule { + + @Info("The start value used for the epsilon value which is applied during the survivor selection.") + @Constant(value = "epsilonSample", namespace = ESamplingSurvivorGeneration.class) + protected double epsilonSample = 0.0; + @Info("The start value used for the adaption of the epsilon value which is applied during the survivor selection.") + @Constant(value = "epsilonSampleDelta", namespace = ESamplingSurvivorGeneration.class) + protected double epsilonSampleDelta = 0.005; + @Info("The maximal value used for the epsilon value which is applied during the survivor selection.") + @Constant(value = "epsilonSampleDeltaMax", namespace = ESamplingSurvivorGeneration.class) + protected double epsilonSampleDeltaMax = 0.005; + @Info("The minimal value used for the epsilon value which is applied during the survivor selection.") + @Constant(value = "epsilonSampleDeltaMin", namespace = ESamplingSurvivorGeneration.class) + protected double epsilonSampleDeltaMin = 0.0001; + + @Override + protected void config() { + bindSelector(EpsilonSamplingSelector.class); + } + + public double getEpsilonSample() { + return epsilonSample; + } + + public void setEpsilonSample(double epsilonSample) { + this.epsilonSample = epsilonSample; + } + + public double getEpsilonSampleDelta() { + return epsilonSampleDelta; + } + + public void setEpsilonSampleDelta(double epsilonSampleDelta) { + this.epsilonSampleDelta = epsilonSampleDelta; + } + + public double getEpsilonSampleDeltaMax() { + return epsilonSampleDeltaMax; + } + + public void setEpsilonSampleDeltaMax(double epsilonSampleDeltaMax) { + this.epsilonSampleDeltaMax = epsilonSampleDeltaMax; + } + + public double getEpsilonSampleDeltaMin() { + return epsilonSampleDeltaMin; + } + + public void setEpsilonSampleDeltaMin(double epsilonSampleDeltaMin) { + this.epsilonSampleDeltaMin = epsilonSampleDeltaMin; + } + +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/NeighborhoodScheduler.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/NeighborhoodScheduler.java new file mode 100644 index 00000000..7aba0e3c --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/NeighborhoodScheduler.java @@ -0,0 +1,25 @@ +package org.opt4j.optimizers.ea.aeseh; + +import java.util.Set; + +import org.opt4j.core.Individual; + +/** + * The {@link NeighborhoodScheduler} manages the schedule according to which the + * neighborhoods are chosen by the {@link EpsilonNeighborhoodCoupler} to pick the crossover + * parents. + * + * @author Fedor Smirnov + * + */ +public interface NeighborhoodScheduler { + + /** + * Returns a copy of the neighborhood that shall be used for the creation of + * the next pair of parents. + * + * @return copy of the neighborhood set + */ + public Set next(); + +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/NeighborhoodSchedulerRoundRobin.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/NeighborhoodSchedulerRoundRobin.java new file mode 100644 index 00000000..8e7f1de7 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/NeighborhoodSchedulerRoundRobin.java @@ -0,0 +1,31 @@ +package org.opt4j.optimizers.ea.aeseh; + +import java.util.List; +import java.util.Set; + +import org.opt4j.core.Individual; + +/** + * The {@link NeighborhoodSchedulerRoundRobin} schedules the neighborhoods in a simple + * round-robin fashion. + * + * @author Fedor Smirnov + * + */ +public class NeighborhoodSchedulerRoundRobin implements NeighborhoodScheduler { + + protected final List> neighborhoods; + protected int nextIdx; + + public NeighborhoodSchedulerRoundRobin(List> neighborhoods) { + this.neighborhoods = neighborhoods; + this.nextIdx = 0; + } + + @Override + public Set next() { + Set neighborhood = neighborhoods.get(nextIdx); + nextIdx = (nextIdx + 1) % neighborhoods.size(); + return neighborhood; + } +} diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/package-info.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/package-info.java new file mode 100644 index 00000000..0d527e87 --- /dev/null +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/aeseh/package-info.java @@ -0,0 +1,36 @@ +/** + * + *

+ * Package for the classes of the Adaptive ε-Sampling ε-Hood MOEA. This + * evolutionary algorithm performs both the survivor and the parent selection + * based on ε-sampling. + *

+ *

+ * The {@link org.opt4j.optimizers.ea.aeseh.EpsilonSamplingSelector} uses the + * {@link org.opt4j.optimizers.ea.aeseh.ESamplingSurvivorGeneration} to pick the + * individuals that form the pool of possible parents. Hereby, ε-dominant + * {@link org.opt4j.core.Individual}s are preferred. The check for ε-dominance + * is implemented by enhancing the {@link org.opt4j.core.Objectives} of the + * considered individual using the + * {@link org.opt4j.optimizers.ea.aeseh.EpsilonMapping}. For the parent + * selection, the {@link org.opt4j.optimizers.ea.aeseh.EpsilonNeighborhoodCoupler} divides the + * survivor pool based on ε-dominance among the survivors. A pair of parents is + * then always picked from the same neighborhood, while the arbitration of the + * neighborhoods to pick from is handled by the + * {@link org.opt4j.optimizers.ea.aeseh.NeighborhoodScheduler}. + *

+ *

+ * Throughout the exploration, the ε-values used for the survivor generation and + * the parent selection are adjusted in order to pick a number of ε-dominant + * survivors that equals the number of individuals that are to be generated in + * each generation and to create a number of neighborhoods close to a number + * provided by the user. The adaptation of the ε-values is hereby managed by the + * {@link org.opt4j.optimizers.ea.aeseh.EpsilonAdaptation}. + *

+ *

+ * In the Opt4J GUI, the AeSeH is configured using the + * {@link org.opt4j.optimizers.ea.aeseh.AeSeHModule} and the + * {@link org.opt4j.optimizers.ea.EvolutionaryAlgorithmModule}. + *

+ */ +package org.opt4j.optimizers.ea.aeseh; diff --git a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/package-info.java b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/package-info.java index f9997c73..cdf90603 100644 --- a/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/package-info.java +++ b/opt4j-optimizers/src/main/java/org/opt4j/optimizers/ea/package-info.java @@ -24,4 +24,4 @@ /** * Provides the classes for a (Multi-Objective) Evolutionary Algorithm (MOEA). */ -package org.opt4j.optimizers.ea; \ No newline at end of file +package org.opt4j.optimizers.ea; diff --git a/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/NonDominatedFrontsTest.java b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/NonDominatedFrontsTest.java new file mode 100644 index 00000000..38ea0c7e --- /dev/null +++ b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/NonDominatedFrontsTest.java @@ -0,0 +1,83 @@ +package org.opt4j.optimizers.ea; + +import static org.junit.Assert.*; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.Test; +import org.opt4j.core.Individual; +import org.opt4j.core.Objective; +import org.opt4j.core.Objective.Sign; + +import org.opt4j.core.Objectives; + +import static org.mockito.Mockito.*; + +public class NonDominatedFrontsTest { + + protected static final Objective firstObj = new Objective("first", Sign.MAX); + protected static final Objective secondObj = new Objective("second", Sign.MAX); + + protected static Individual first = mock(Individual.class); + protected static Individual second = mock(Individual.class); + protected static Individual third = mock(Individual.class); + protected static Individual fourth = mock(Individual.class); + protected static Individual fifth = mock(Individual.class); + protected static Individual infeasible = mock(Individual.class); + + protected static Set getIndividualSet() { + Set result = new HashSet(); + + when(first.getObjectives()).thenReturn(getObjectives(5, 1)); + when(second.getObjectives()).thenReturn(getObjectives(3, 3)); + when(third.getObjectives()).thenReturn(getObjectives(1, 4)); + when(fourth.getObjectives()).thenReturn(getObjectives(3, 2)); + when(fifth.getObjectives()).thenReturn(getObjectives(1, 1)); + + Objectives insfeasibleObjectives = new Objectives(); + insfeasibleObjectives.add(firstObj, Objective.INFEASIBLE); + insfeasibleObjectives.add(secondObj, Objective.INFEASIBLE); + when(infeasible.getObjectives()).thenReturn(insfeasibleObjectives); + + when(first.toString()).thenReturn("first"); + when(second.toString()).thenReturn("second"); + when(third.toString()).thenReturn("third"); + when(fourth.toString()).thenReturn("fourth"); + when(fifth.toString()).thenReturn("fifth"); + when(infeasible.toString()).thenReturn("infeasible"); + + result.add(first); + result.add(second); + result.add(third); + result.add(fourth); + result.add(fifth); + result.add(infeasible); + + return result; + } + + protected static Objectives getObjectives(int f, int s) { + Objectives result = new Objectives(); + result.add(firstObj, f); + result.add(secondObj, s); + return result; + } + + @Test + public void testGenerateFronts() { + NonDominatedFronts fronts = new NonDominatedFronts(getIndividualSet()); + assertEquals(4, fronts.getFrontNumber()); + + assertEquals(3, fronts.getFrontAtIndex(0).size()); + assertTrue(fronts.getFrontAtIndex(0).contains(first)); + assertTrue(fronts.getFrontAtIndex(0).contains(second)); + assertTrue(fronts.getFrontAtIndex(0).contains(third)); + + assertEquals(1, fronts.getFrontAtIndex(1).size()); + assertTrue(fronts.getFrontAtIndex(1).contains(fourth)); + + assertEquals(1, fronts.getFrontAtIndex(2).size()); + assertTrue(fronts.getFrontAtIndex(2).contains(fifth)); + } +} diff --git a/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/AdaptiveEpsilonTest.java b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/AdaptiveEpsilonTest.java new file mode 100644 index 00000000..4920e6a6 --- /dev/null +++ b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/AdaptiveEpsilonTest.java @@ -0,0 +1,35 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class AdaptiveEpsilonTest { + + @Test(expected = IllegalArgumentException.class) + public void testWrongInput2() { + new AdaptiveEpsilon(0.1, -.1, 0.4, 0.0); + } + + @Test(expected = IllegalArgumentException.class) + public void testWrongInput() { + new AdaptiveEpsilon(0.1, 0.5, 0.4, 0.0); + } + + @Test + public void test() { + AdaptiveEpsilon adaptEps = new AdaptiveEpsilon(0.1, 0.2, 0.4, 0.0); + assertEquals(.1, adaptEps.getEpsilon(), 0.0); + assertEquals(.2, adaptEps.getEpsilonDelta(), 0.0); + assertEquals(.4, adaptEps.getEpsilonDeltaMax(), 0.0); + assertEquals(.0, adaptEps.getEpsilonDeltaMin(), 0.0); + adaptEps.setEpsilon(.5); + assertEquals(.5, adaptEps.getEpsilon(), 0.0); + adaptEps.setEpsilonDelta(.3); + assertEquals(.3, adaptEps.getEpsilonDelta(), 0.0); + adaptEps.setEpsilonDelta(.5); + assertEquals(.4, adaptEps.getEpsilonDelta(), 0.0); + adaptEps.setEpsilonDelta(-.1); + assertEquals(.0, adaptEps.getEpsilonDelta(), 0.0); + } +} diff --git a/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGenerationBasicTest1.java b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGenerationBasicTest1.java new file mode 100644 index 00000000..a216306f --- /dev/null +++ b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGenerationBasicTest1.java @@ -0,0 +1,154 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.opt4j.core.Individual; +import org.opt4j.core.Objective; +import org.opt4j.core.Objectives; +import org.opt4j.core.Objective.Sign; +import org.opt4j.optimizers.ea.aeseh.EpsilonMappingAdditive; +import org.opt4j.optimizers.ea.aeseh.EpsilonAdaptationDelta; +import org.opt4j.optimizers.ea.NonDominatedFronts; +import org.opt4j.optimizers.ea.aeseh.ESamplingSurvivorGenerationBasic; +import org.opt4j.optimizers.ea.aeseh.EpsilonAdaptation; + +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +public class ESamplingSurvivorGenerationBasicTest1 { + + protected static Individual extremeIndividual = mock(Individual.class); + + protected static Objectives dominant = mock(Objectives.class); + protected static Objectives dominated = mock(Objectives.class); + + protected static Individual first = mock(Individual.class); + protected static Individual second = mock(Individual.class); + protected static Individual third = mock(Individual.class); + protected static Individual fourth = mock(Individual.class); + protected static Individual fifth = mock(Individual.class); + + protected static final Objective firstObj = new Objective("first", Sign.MAX); + protected static final Objective secondObj = new Objective("second", Sign.MAX); + + protected static Objectives getObjectives(int f, int s) { + Objectives result = new Objectives(); + result.add(firstObj, f); + result.add(secondObj, s); + return result; + } + + public static List getFirstFront() { + List result = new ArrayList(); + result.add(extremeIndividual); + + when(dominant.dominates(dominated)).thenReturn(true); + when(dominant.dominates(dominant)).thenReturn(false); + when(dominated.dominates(dominated)).thenReturn(false); + when(dominated.dominates(dominated)).thenReturn(false); + + when(first.getObjectives()).thenReturn(dominant); + when(second.getObjectives()).thenReturn(dominant); + when(third.getObjectives()).thenReturn(dominated); + when(fourth.getObjectives()).thenReturn(dominated); + when(fifth.getObjectives()).thenReturn(dominated); + + result.add(first); + result.add(second); + result.add(third); + result.add(fourth); + result.add(fifth); + return result; + } + + public static List> getFronts() { + Individual first = mock(Individual.class); + Individual second = mock(Individual.class); + Individual third = mock(Individual.class); + Individual fourth = mock(Individual.class); + Individual fifth = mock(Individual.class); + Individual sixth = mock(Individual.class); + + List> fronts = new ArrayList>(); + List firstFront = new ArrayList(); + firstFront.add(first); + firstFront.add(second); + firstFront.add(third); + List secondFront = new ArrayList(); + secondFront.add(fourth); + List thirdFront = new ArrayList(); + thirdFront.add(fifth); + thirdFront.add(sixth); + fronts.add(firstFront); + fronts.add(secondFront); + fronts.add(thirdFront); + return fronts; + } + + @Test + public void testAddDominatedSurvivors() { + Random mockRandom = mock(Random.class); + when(mockRandom.nextInt(2)).thenReturn(0); + EpsilonAdaptation mockAdaption = mock(EpsilonAdaptationDelta.class); + ESamplingSurvivorGenerationBasic survivorGeneration = new ESamplingSurvivorGenerationBasic(mockRandom, + new EpsilonMappingAdditive(), mockAdaption, 0.0, .0, .0, .0); + List> frontsMock = getFronts(); + NonDominatedFronts mockFronts = mock(NonDominatedFronts.class); + when(mockFronts.getFrontNumber()).thenReturn(3); + when(mockFronts.getFrontAtIndex(0)).thenReturn(frontsMock.get(0)); + when(mockFronts.getFrontAtIndex(1)).thenReturn(frontsMock.get(1)); + when(mockFronts.getFrontAtIndex(2)).thenReturn(frontsMock.get(2)); + Set survivors = survivorGeneration.addDominatedSurvivors(5, mockFronts); + assertEquals(5, survivors.size()); + assertTrue(survivors.containsAll(frontsMock.get(0))); + assertTrue(survivors.containsAll(frontsMock.get(1))); + verify(mockRandom).nextInt(2); + + } + + @Test + public void testGetExtremeInividuals(){ + Set indis = new HashSet(); + + when(first.getObjectives()).thenReturn(getObjectives(5, 1)); + when(second.getObjectives()).thenReturn(getObjectives(3, 3)); + when(third.getObjectives()).thenReturn(getObjectives(1, 4)); + when(fourth.getObjectives()).thenReturn(getObjectives(3, 2)); + when(fifth.getObjectives()).thenReturn(getObjectives(1, 1)); + + Objectives insfeasibleObjectives = new Objectives(); + insfeasibleObjectives.add(firstObj, Objective.INFEASIBLE); + insfeasibleObjectives.add(secondObj, Objective.INFEASIBLE); + + when(first.toString()).thenReturn("first"); + when(second.toString()).thenReturn("second"); + when(third.toString()).thenReturn("third"); + when(fourth.toString()).thenReturn("fourth"); + when(fifth.toString()).thenReturn("fifth"); + + indis.add(first); + indis.add(second); + indis.add(third); + indis.add(fourth); + indis.add(fifth); + + Random mockRandom = mock(Random.class); + when(mockRandom.nextInt(2)).thenReturn(0); + EpsilonAdaptation mockAdaption = mock(EpsilonAdaptationDelta.class); + ESamplingSurvivorGenerationBasic survivorGeneration = new ESamplingSurvivorGenerationBasic(mockRandom, + new EpsilonMappingAdditive(), mockAdaption, 0.0, .0, .0, .0); + + Collection extremes = survivorGeneration.getExtremeIndividuals(indis); + assertEquals(2, extremes.size()); + assertTrue(extremes.contains(first)); + assertTrue(extremes.contains(third)); + } + +} diff --git a/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGenerationBasicTest2.java b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGenerationBasicTest2.java new file mode 100644 index 00000000..0b27ee2e --- /dev/null +++ b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/ESamplingSurvivorGenerationBasicTest2.java @@ -0,0 +1,150 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.opt4j.core.Individual; +import org.opt4j.core.Objective; +import org.opt4j.core.Objective.Sign; +import org.opt4j.optimizers.ea.aeseh.EpsilonMappingAdditive; +import org.opt4j.optimizers.ea.aeseh.ESamplingSurvivorGenerationBasic; +import org.opt4j.optimizers.ea.aeseh.EpsilonAdaptation; +import org.opt4j.optimizers.ea.aeseh.EpsilonMapping; +import org.opt4j.core.Objectives; + +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +public class ESamplingSurvivorGenerationBasicTest2 { + + protected Objective firstObj = new Objective("first", Sign.MAX); + protected Objective secondObj = new Objective("second", Sign.MAX); + + protected List nonDominated; + + protected Individual firstIndi; + protected Individual secondIndi; + protected Individual thirdIndi; + protected Individual fourthIndi; + + protected Random rand; + + protected EpsilonMapping mapping; + + protected EpsilonAdaptation adaptation; + + protected Individual getIndividual(double firstValue, double secondValue) { + Individual result = mock(Individual.class); + Objectives objectives = new Objectives(); + objectives.add(firstObj, firstValue); + objectives.add(secondObj, secondValue); + when(result.getObjectives()).thenReturn(objectives); + return result; + } + + @Before + public void init() { + nonDominated = new ArrayList(); + firstIndi = getIndividual(1.0, 5.0); + secondIndi = getIndividual(1.1, 3.0); + thirdIndi = getIndividual(3.0, 1.1); + fourthIndi = getIndividual(5.0, 1.0); + nonDominated.add(firstIndi); + nonDominated.add(fourthIndi); + nonDominated.add(secondIndi); + nonDominated.add(thirdIndi); + + rand = mock(Random.class); + when(rand.nextInt(anyInt())).thenReturn(0); + + adaptation = mock(EpsilonAdaptation.class); + mapping = new EpsilonMappingAdditive(); + } + + @Test + public void testGetSurvivors() { + Set extremes = new HashSet(); + Individual firstExtreme = getIndividual(0.0, 6.0); + Individual secondExtreme = getIndividual(6.0, 0.0); + Individual dominated = getIndividual(1.0, 1.0); + extremes.add(firstExtreme); + extremes.add(secondExtreme); + Set population = new HashSet(nonDominated); + population.addAll(extremes); + population.add(dominated); + ESamplingSurvivorGenerationBasic survivorGeneration = new ESamplingSurvivorGenerationBasic(rand, mapping, + adaptation, .2, .0, .0, .0); + Set survivors = survivorGeneration.getSurvivors(population, 3); + assertEquals(3, survivors.size()); + assertTrue(survivors.contains(firstExtreme)); + assertTrue(survivors.contains(secondExtreme)); + assertFalse(survivors.contains(dominated)); + + survivors = survivorGeneration.getSurvivors(population, 7); + assertEquals(7, survivors.size()); + assertTrue(survivors.contains(firstExtreme)); + assertTrue(survivors.contains(secondExtreme)); + assertTrue(survivors.contains(dominated)); + } + + @Test + public void addNonDominatedSurvivorsTestTooFewSurvivors() { + Set extremes = new HashSet(); + Individual firstExtreme = getIndividual(0.0, 6.0); + Individual secondExtreme = getIndividual(6.0, 0.0); + // make sure we always have epsilon-dominance + Individual addition = getIndividual(1.2, 2.9); + extremes.add(firstExtreme); + extremes.add(secondExtreme); + Set firstFront = new HashSet(nonDominated); + firstFront.addAll(extremes); + firstFront.add(addition); + ESamplingSurvivorGenerationBasic survivorGeneration = new ESamplingSurvivorGenerationBasic(rand, mapping, + adaptation, .2, .0, .0, .0); + Set survivors = survivorGeneration.addNonDominatedSurvivors(extremes, firstFront, 7); + assertEquals(7, survivors.size()); + assertTrue(survivors.contains(firstExtreme)); + assertTrue(survivors.contains(secondExtreme)); + verify(adaptation).adaptEpsilon(survivorGeneration.adaptiveEpsilonSampling, true); + } + + @Test + public void addNonDominatedSurvivorsTestTooManySurvivors() { + Set extremes = new HashSet(); + Individual firstExtreme = getIndividual(0.0, 6.0); + Individual secondExtreme = getIndividual(6.0, 0.0); + extremes.add(firstExtreme); + extremes.add(secondExtreme); + Set firstFront = new HashSet(nonDominated); + firstFront.addAll(extremes); + ESamplingSurvivorGenerationBasic survivorGeneration = new ESamplingSurvivorGenerationBasic(rand, mapping, + adaptation, .2, .0, .0, .0); + Set survivors = survivorGeneration.addNonDominatedSurvivors(extremes, firstFront, 3); + assertEquals(3, survivors.size()); + assertTrue(survivors.contains(firstExtreme)); + assertTrue(survivors.contains(secondExtreme)); + verify(adaptation).adaptEpsilon(survivorGeneration.adaptiveEpsilonSampling, false); + } + + @Test + public void testApplyEpsilonSampling() { + ESamplingSurvivorGenerationBasic survivorGeneration = new ESamplingSurvivorGenerationBasic(rand, mapping, + adaptation, .2, .0, .0, .0); + Set dominant = new HashSet(); + Set dominated = new HashSet(); + survivorGeneration.applyEpsilonSampling(nonDominated, dominant, dominated, 0.05); + assertEquals(2, dominant.size()); + assertEquals(2, dominated.size()); + assertTrue(dominant.contains(firstIndi)); + assertTrue(dominant.contains(fourthIndi)); + assertTrue(dominated.contains(secondIndi)); + assertTrue(dominated.contains(thirdIndi)); + } + +} diff --git a/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonAdaptationDeltaTest.java b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonAdaptationDeltaTest.java new file mode 100644 index 00000000..3858d451 --- /dev/null +++ b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonAdaptationDeltaTest.java @@ -0,0 +1,32 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.opt4j.optimizers.ea.aeseh.EpsilonAdaptationDelta; + +public class EpsilonAdaptationDeltaTest { + + protected double eps_start = 0.0; + protected double eps_delta_start = 0.5; + protected double eps_delta_max = 1.5; + protected double eps_delta_min = 0.4; + + @Test + public void testEpsilonSample() { + EpsilonAdaptationDelta adaptation = new EpsilonAdaptationDelta(); + AdaptiveEpsilon adaptiveEpsilon = new AdaptiveEpsilon(eps_start, eps_delta_start, eps_delta_max, eps_delta_min); + adaptation.adaptEpsilon(adaptiveEpsilon, false); + assertEquals(0.5, adaptiveEpsilon.getEpsilon(), 0.0); + assertEquals(1.0, adaptiveEpsilon.getEpsilonDelta(), 0.0); + adaptation.adaptEpsilon(adaptiveEpsilon, false); + assertEquals(1.5, adaptiveEpsilon.getEpsilonDelta(), 0.0); + assertEquals(1.5, adaptiveEpsilon.getEpsilon(), 0.0); + adaptation.adaptEpsilon(adaptiveEpsilon, true); + assertEquals(0.0, adaptiveEpsilon.getEpsilon(), 0.0); + assertEquals(0.75, adaptiveEpsilon.epsilonDelta, 0.0); + adaptation.adaptEpsilon(adaptiveEpsilon, true); + assertEquals(-.75, adaptiveEpsilon.getEpsilon(), 0.0); + assertEquals(0.4, adaptiveEpsilon.epsilonDelta, 0.0); + } +} diff --git a/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonMappingAdditiveTest.java b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonMappingAdditiveTest.java new file mode 100644 index 00000000..7689736f --- /dev/null +++ b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonMappingAdditiveTest.java @@ -0,0 +1,84 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.opt4j.core.Individual; +import org.opt4j.core.Objective; +import org.opt4j.core.Objective.Sign; +import org.opt4j.optimizers.ea.aeseh.EpsilonMappingAdditive; +import org.opt4j.core.Objectives; + +import static org.mockito.Mockito.*; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class EpsilonMappingAdditiveTest { + + protected static final Objective first = new Objective("first", Sign.MAX); + protected static final Objective second = new Objective("second", Sign.MIN); + + protected static Set getIndividualSet() { + Objectives firstObj = new Objectives(); + firstObj.add(first, 4); + firstObj.add(second, 3); + + Objectives secondObj = new Objectives(); + secondObj.add(first, 1); + secondObj.add(second, 1); + + Objectives thirdObj = new Objectives(); + thirdObj.add(first, 2); + thirdObj.add(second, 2); + + Objectives infeasibleObj = new Objectives(); + infeasibleObj.add(first, Objective.INFEASIBLE); + infeasibleObj.add(second, Objective.INFEASIBLE); + + Individual first = mock(Individual.class); + when(first.getObjectives()).thenReturn(firstObj); + Individual second = mock(Individual.class); + when(second.getObjectives()).thenReturn(secondObj); + Individual third = mock(Individual.class); + when(third.getObjectives()).thenReturn(thirdObj); + Individual infeasible = mock(Individual.class); + when(infeasible.getObjectives()).thenReturn(infeasibleObj); + + Set indis = new HashSet(); + indis.add(first); + indis.add(second); + indis.add(third); + indis.add(infeasible); + return indis; + } + + @Test + public void testMapObjectives() { + Set indis = getIndividualSet(); + EpsilonMappingAdditive epsilonMapping = new EpsilonMappingAdditive(); + Map amplitudeMap = epsilonMapping.findObjectiveAmplitudes(indis); + + Objectives objectives = new Objectives(); + objectives.add(first, 0); + objectives.add(second, 0); + double epsilon = 0.5; + + Objectives epsilonEnhanced = epsilonMapping.mapObjectives(objectives, epsilon, amplitudeMap); + assertEquals(1.5, epsilonEnhanced.get(first).getDouble(), 0.0); + assertEquals(-1.0, epsilonEnhanced.get(second).getDouble(), 0.0); + } + + @Test + public void testFindAmplitudes() { + Set indis = getIndividualSet(); + EpsilonMappingAdditive epsilonMapping = new EpsilonMappingAdditive(); + Map amplitudeMap = epsilonMapping.findObjectiveAmplitudes(indis); + assertTrue(amplitudeMap.containsKey(first)); + assertTrue(amplitudeMap.containsKey(second)); + assertEquals(3.0, amplitudeMap.get(first), 0.0); + assertEquals(2.0, amplitudeMap.get(second), 0.0); + } + +} diff --git a/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonNeighborhoodCouplerTest.java b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonNeighborhoodCouplerTest.java new file mode 100644 index 00000000..2ec306ce --- /dev/null +++ b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonNeighborhoodCouplerTest.java @@ -0,0 +1,102 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.opt4j.core.Individual; +import org.opt4j.core.Objective; +import org.opt4j.core.Objectives; +import org.opt4j.core.Objective.Sign; +import org.opt4j.operators.crossover.Pair; +import org.opt4j.optimizers.ea.aeseh.EpsilonMappingAdditive; +import org.opt4j.optimizers.ea.aeseh.EpsilonNeighborhoodCoupler; +import org.opt4j.optimizers.ea.aeseh.EpsilonAdaptationDelta; +import org.opt4j.optimizers.ea.aeseh.EpsilonAdaptation; + +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; + +public class EpsilonNeighborhoodCouplerTest { + + protected static EpsilonAdaptation mockAdaption = mock(EpsilonAdaptationDelta.class); + + protected static Objective firstObj = new Objective("first", Sign.MAX); + protected static Objective secondObj = new Objective("second", Sign.MAX); + + protected static Individual first = mock(Individual.class); + protected static Individual second = mock(Individual.class); + protected static Individual third = mock(Individual.class); + protected static Individual fourth = mock(Individual.class); + + protected static List getSurvivors() { + List survivors = new ArrayList(); + when(first.getObjectives()).thenReturn(getObj(5, 1)); + survivors.add(first); + when(second.getObjectives()).thenReturn(getObj(3, 3)); + survivors.add(second); + when(third.getObjectives()).thenReturn(getObj(1, 4)); + survivors.add(third); + when(fourth.getObjectives()).thenReturn(getObj(3, 3)); + survivors.add(fourth); + return survivors; + } + + protected static EpsilonNeighborhoodCoupler makeDefaultCoupler() { + return new EpsilonNeighborhoodCoupler(new EpsilonMappingAdditive(), mockAdaption, new Random(), 10, 0.0001, 0, 0, 0); + } + + protected static Objectives getObj(int firstValue, int secondValue) { + Objectives result = new Objectives(); + result.add(firstObj, firstValue); + result.add(secondObj, secondValue); + return result; + } + + @Test + public void testGetCouples() { + EpsilonNeighborhoodCoupler coupler = makeDefaultCoupler(); + Collection> couples = coupler.getCouples(2, getSurvivors()); + assertEquals(2, couples.size()); + } + + @Test + public void testCreateNeighborhoods() { + EpsilonNeighborhoodCoupler coupler = makeDefaultCoupler(); + Map amplitudeMap = new HashMap(); + amplitudeMap.put(firstObj, 0.001); + amplitudeMap.put(secondObj, 0.001); + List survivors = getSurvivors(); + List> neighborhoods = coupler.createNeighborhoods(survivors); + assertEquals(3, neighborhoods.size()); + verify(mockAdaption).adaptEpsilon(coupler.adaptiveEpsilonNeighborhood, true); + coupler = new EpsilonNeighborhoodCoupler(new EpsilonMappingAdditive(), mockAdaption, new Random(), 2, 0.0001, 0, 0, 0); + neighborhoods = coupler.createNeighborhoods(survivors); + verify(mockAdaption).adaptEpsilon(coupler.adaptiveEpsilonNeighborhood, false); + } + + @Test + public void testGetCouple() { + EpsilonNeighborhoodCoupler coupler = makeDefaultCoupler(); + Individual first = mock(Individual.class); + Individual second = mock(Individual.class); + Set indiSet = new HashSet(); + indiSet.add(first); + indiSet.add(second); + Pair couple = coupler.pickCouple(indiSet); + assertTrue(couple.getFirst().equals(first) || couple.getSecond().equals(first)); + assertTrue(couple.getFirst().equals(second) || couple.getSecond().equals(second)); + indiSet.remove(first); + couple = coupler.pickCouple(indiSet); + assertTrue(couple.getFirst().equals(second)); + assertTrue(couple.getSecond().equals(second)); + } + +} diff --git a/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonSamplingSelectorTest.java b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonSamplingSelectorTest.java new file mode 100644 index 00000000..4b6d6959 --- /dev/null +++ b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/EpsilonSamplingSelectorTest.java @@ -0,0 +1,60 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.junit.Assert.*; + +import org.junit.Test; +import org.opt4j.core.Individual; +import org.opt4j.core.optimizer.Population; +import org.opt4j.optimizers.ea.aeseh.EpsilonSamplingSelector; +import org.opt4j.optimizers.ea.aeseh.ESamplingSurvivorGenerationBasic; +import org.opt4j.optimizers.ea.aeseh.ESamplingSurvivorGeneration; + +import static org.mockito.Mockito.*; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class EpsilonSamplingSelectorTest { + + @Test + public void testGetParents(){ + ESamplingSurvivorGeneration survivorGeneration = mock(ESamplingSurvivorGenerationBasic.class); + EpsilonSamplingSelector selector = new EpsilonSamplingSelector(survivorGeneration); + Individual first = mock(Individual.class); + Individual second = mock(Individual.class); + Individual third = mock(Individual.class); + Population population = new Population(); + population.add(first); + population.add(second); + population.add(third); + Collection parents = selector.getParents(3, population); + assertEquals(population, parents); + } + + @Test + public void testGetLames() { + ESamplingSurvivorGeneration survivorGeneration = mock(ESamplingSurvivorGenerationBasic.class); + EpsilonSamplingSelector selector = new EpsilonSamplingSelector(survivorGeneration); + + Individual first = mock(Individual.class); + Individual second = mock(Individual.class); + Individual third = mock(Individual.class); + + Set survivors = new HashSet(); + survivors.add(first); + survivors.add(second); + + Population population = new Population(); + population.add(first); + population.add(second); + population.add(third); + + when(survivorGeneration.getSurvivors(population, 2)).thenReturn(survivors); + Collection result = selector.getLames(1, population); + verify(survivorGeneration).getSurvivors(population, 2); + assertEquals(1, result.size()); + assertTrue(result.contains(third)); + } + +} diff --git a/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/NeighborhoodSchedulerRoundRobinTest.java b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/NeighborhoodSchedulerRoundRobinTest.java new file mode 100644 index 00000000..d0899f41 --- /dev/null +++ b/opt4j-optimizers/src/test/java/org/opt4j/optimizers/ea/aeseh/NeighborhoodSchedulerRoundRobinTest.java @@ -0,0 +1,43 @@ +package org.opt4j.optimizers.ea.aeseh; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Test; +import org.opt4j.core.Individual; +import org.opt4j.optimizers.ea.aeseh.NeighborhoodSchedulerRoundRobin; + +public class NeighborhoodSchedulerRoundRobinTest { + + @SuppressWarnings("serial") + class MockNeighborhood extends HashSet{ + protected final String name; + public MockNeighborhood(String name){ + this.name = name; + } + @Override + public String toString() { + return name; + } + } + + @Test + public void test() { + MockNeighborhood first = new MockNeighborhood("first"); + MockNeighborhood second = new MockNeighborhood("second"); + MockNeighborhood third = new MockNeighborhood("third"); + List> neighborhoods = new ArrayList>(); + neighborhoods.add(first); + neighborhoods.add(second); + neighborhoods.add(third); + NeighborhoodSchedulerRoundRobin scheduler = new NeighborhoodSchedulerRoundRobin(neighborhoods); + assertEquals(scheduler.next().toString(), "first"); + assertEquals(scheduler.next().toString(), "second"); + assertEquals(scheduler.next().toString(), "third"); + assertEquals(scheduler.next().toString(), "first"); + } +}