iDynoOptimizer.MOEAFramework26.src.org.moeaframework.algorithm.MOEAD.java Source code

Java tutorial

Introduction

Here is the source code for iDynoOptimizer.MOEAFramework26.src.org.moeaframework.algorithm.MOEAD.java

Source

/* Copyright 2009-2015 David Hadka
 *
 * This file is part of the MOEA Framework.
 *
 * The MOEA Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The MOEA Framework is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with the MOEA Framework.  If not, see <http://www.gnu.org/licenses/>.
 */
package iDynoOptimizer.MOEAFramework26.src.org.moeaframework.algorithm;

import java.io.NotSerializableException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.math3.util.MathArrays;
import iDynoOptimizer.MOEAFramework26.src.org.moeaframework.core.FrameworkException;
import iDynoOptimizer.MOEAFramework26.src.org.moeaframework.core.Initialization;
import iDynoOptimizer.MOEAFramework26.src.org.moeaframework.core.NondominatedPopulation;
import iDynoOptimizer.MOEAFramework26.src.org.moeaframework.core.PRNG;
import iDynoOptimizer.MOEAFramework26.src.org.moeaframework.core.Problem;
import iDynoOptimizer.MOEAFramework26.src.org.moeaframework.core.Solution;
import iDynoOptimizer.MOEAFramework26.src.org.moeaframework.core.Variation;
import iDynoOptimizer.MOEAFramework26.src.org.moeaframework.util.weights.RandomGenerator;
import iDynoOptimizer.MOEAFramework26.src.org.moeaframework.util.weights.WeightGenerator;

/**
 * Implementation of MOEA/D, the multiobjective evolutionary algorithm with
 * decomposition.  This implementation supports both the original MOEA/D
 * specification from [1] as well as the utility-based search extension from
 * [2].
 * <p>
 * References:
 * <ol>
 * <li>Li, H. and Zhang, Q. "Multiobjective Optimization problems with
 * Complicated Pareto Sets, MOEA/D and NSGA-II." IEEE Transactions on
 * Evolutionary Computation, 13(2):284-302, 2009.
 * <li>Zhang, Q., et al.  "The Performance of a New Version of MOEA/D on
 * CEC09 Unconstrained MOP Test Instances."  IEEE Congress on Evolutionary
 * Computation, 2009.
 * </ol>
 */
public class MOEAD extends AbstractAlgorithm {

    /**
     * Represents an individual (population slot) in the MOEA/D algorithm.
     */
    private static class Individual implements Serializable {

        private static final long serialVersionUID = 868794189268472009L;

        /**
         * The current solution occupying this individual.
         */
        private Solution solution;

        /**
         * The Chebyshev weights for this individual.
         */
        private double[] weights;

        /**
         * The neighborhood of this individual.
         */
        private List<Individual> neighbors;

        /**
         * The utility of this individual.
         */
        private double utility;

        /**
         * The cached fitness of the solution currently occupying this
         * individual when the utility was last updated.
         */
        private double fitness;

        /**
         * Constructs an individual with the specified Chebyshev weights.
         * 
         * @param weights the Chebyshev weights for this individual
         */
        public Individual(double[] weights) {
            this.weights = weights;

            neighbors = new ArrayList<Individual>();
            utility = 1.0;
        }

        /**
         * Returns the current solution occupying this individual.
         * 
         * @return the current solution occupying this individual
         */
        public Solution getSolution() {
            return solution;
        }

        /**
         * Sets the current solution occupying this individual.
         * 
         * @param solution the new solution occupying this individual
         */
        public void setSolution(Solution solution) {
            this.solution = solution;
        }

        /**
         * Returns the Chebyshev weights for this individual.
         * 
         * @return the Chebyshev weights for this individual
         */
        public double[] getWeights() {
            return weights;
        }

        /**
         * Returns the neighborhood of this individual.
         * 
         * @return the neighborhood of this individual
         */
        public List<Individual> getNeighbors() {
            return neighbors;
        }

        /**
         * Adds a neighboring individual to the neighborhood of this individual.
         * 
         * @param neighbor the individual to be added to the neighborhood
         */
        public void addNeighbor(Individual neighbor) {
            neighbors.add(neighbor);
        }

        /**
         * Returns the utility of this individual.
         * 
         * @return the utility of this individual
         */
        public double getUtility() {
            return utility;
        }

        /**
         * Sets the utility of this individual.
         * 
         * @param utility the new utility of this individual
         */
        public void setUtility(double utility) {
            this.utility = utility;
        }

        /**
         * Returns the cached fitness of the solution currently occupying this
         * individual when the utility was last updated.
         * 
         * @return the cached fitness of the solution currently occupying this
         *         individual when the utility was last updated
         */
        public double getFitness() {
            return fitness;
        }

        /**
         * Sets the cached fitness of the solution currently occupying this
         * individual when the utility is updated
         * 
         * @param fitness the new fitness of the solution currently occupying
         *        this individual when the utility is updated
         */
        public void setFitness(double fitness) {
            this.fitness = fitness;
        }

    }

    /**
     * Compares individuals based on their distance from a specified individual.
     */
    private static class WeightSorter implements Comparator<Individual> {

        /**
         * The individual from which weight distances are computed.
         */
        private final Individual individual;

        /**
         * Constructs a comparator for comparing individuals based on their 
         * distance from the specified individual.
         * 
         * @param individual the individual from which weight distances are
         *        computed
         */
        public WeightSorter(Individual individual) {
            this.individual = individual;
        }

        @Override
        public int compare(Individual o1, Individual o2) {
            double d1 = MathArrays.distance(individual.getWeights(), o1.getWeights());
            double d2 = MathArrays.distance(individual.getWeights(), o2.getWeights());

            return Double.compare(d1, d2);
        }

    }

    /**
     * The current population.
     */
    private List<Individual> population;

    /**
     * The ideal point; each index stores the best observed value for each
     * objective.
     */
    private double[] idealPoint;

    /**
     * The size of the neighborhood used for mating.
     */
    private final int neighborhoodSize;

    /**
     * The weight generator; or {@code null} if the default weight generator
     * is used.
     */
    private final WeightGenerator weightGenerator;

    /**
     * The probability of mating with a solution in the neighborhood rather
     * than the entire population.
     */
    private final double delta;

    /**
     * The maximum number of population slots a solution can replace.
     */
    private final double eta;

    /**
     * The initialization operator.
     */
    private final Initialization initialization;

    /**
     * The variation operator.
     */
    private final Variation variation;

    /**
     * The frequency, in generations, in which utility values are updated.  Set
     * to {@code -1} to disable utility-based search.
     */
    private final int updateUtility;

    /**
     * The current generation number.
     */
    private int generation;

    /**
     * Constructs the MOEA/D algorithm with the specified components.  This
     * version of MOEA/D uses utility-based search as described in [2].
     * 
     * @param problem the problem being solved
     * @param neighborhoodSize the size of the neighborhood used for mating,
     *        which must be at least {@code variation.getArity()-1}.
     * @param initialization the initialization method
     * @param variation the variation operator
     * @param delta the probability of mating with a solution in the
     *        neighborhood rather than the entire population
     * @param eta the maximum number of population slots a solution can replace
     * @param updateUtility the frequency, in generations, in which utility
     *        values are updated; set to {@code -1} to disable utility-based
     *        search
     */
    public MOEAD(Problem problem, int neighborhoodSize, Initialization initialization, Variation variation,
            double delta, double eta, int updateUtility) {
        this(problem, neighborhoodSize, null, initialization, variation, delta, eta, updateUtility);
    }

    /**
     * Constructs the MOEA/D algorithm with the specified components.  This
     * constructs the original MOEA/D implementation without utility-based
     * search.
     * 
     * @param problem the problem being solved
     * @param neighborhoodSize the size of the neighborhood used for mating,
     *        which must be at least {@code variation.getArity()-1}.
     * @param initialization the initialization method
     * @param variation the variation operator
     * @param delta the probability of mating with a solution in the
     *        neighborhood rather than the entire population
     * @param eta the maximum number of population slots a solution can replace
     */
    public MOEAD(Problem problem, int neighborhoodSize, Initialization initialization, Variation variation,
            double delta, double eta) {
        this(problem, neighborhoodSize, initialization, variation, delta, eta, -1);
    }

    /**
     * Constructs the MOEA/D algorithm with the specified components.  This
     * version of MOEA/D uses utility-based search as described in [2].
     * 
     * @param problem the problem being solved
     * @param neighborhoodSize the size of the neighborhood used for mating,
     *        which must be at least {@code variation.getArity()-1}.
     * @param weightGenerator the weight generator
     * @param initialization the initialization method, which must generate the
     *        same number of solutions as weights
     * @param variation the variation operator
     * @param delta the probability of mating with a solution in the
     *        neighborhood rather than the entire population
     * @param eta the maximum number of population slots a solution can replace
     * @param updateUtility the frequency, in generations, in which utility
     *        values are updated; set to {@code -1} to disable utility-based
     *        search
     */
    public MOEAD(Problem problem, int neighborhoodSize, WeightGenerator weightGenerator,
            Initialization initialization, Variation variation, double delta, double eta, int updateUtility) {
        super(problem);
        this.neighborhoodSize = neighborhoodSize;
        this.weightGenerator = weightGenerator;
        this.initialization = initialization;
        this.variation = variation;
        this.delta = delta;
        this.eta = eta;
        this.updateUtility = updateUtility;
    }

    /**
     * Constructs the MOEA/D algorithm with the specified components.  This
     * constructs the original MOEA/D implementation without utility-based
     * search.
     * 
     * @param problem the problem being solved
     * @param neighborhoodSize the size of the neighborhood used for mating,
     *        which must be at least {@code variation.getArity()-1}.
     * @param weightGenerator the weight generator
     * @param initialization the initialization method, which must generate the
     *        same number of solutions as weights
     * @param variation the variation operator
     * @param delta the probability of mating with a solution in the
     *        neighborhood rather than the entire population
     * @param eta the maximum number of population slots a solution can replace
     */
    public MOEAD(Problem problem, int neighborhoodSize, WeightGenerator weightGenerator,
            Initialization initialization, Variation variation, double delta, double eta) {
        this(problem, neighborhoodSize, weightGenerator, initialization, variation, delta, eta, -1);
    }

    @Override
    public void initialize() {
        super.initialize();

        Solution[] initialSolutions = initialization.initialize();

        initializePopulation(initialSolutions.length);
        initializeNeighborhoods();
        initializeIdealPoint();
        evaluateAll(initialSolutions);

        for (int i = 0; i < initialSolutions.length; i++) {
            Solution solution = initialSolutions[i];
            updateIdealPoint(solution);
            population.get(i).setSolution(solution);
        }

        for (int i = 0; i < initialSolutions.length; i++) {
            population.get(i).setFitness(fitness(population.get(i).getSolution(), population.get(i).getWeights()));
        }
    }

    /**
     * Initializes the population using a procedure attempting to create a
     * uniform distribution of weights.
     * 
     * @param populationSize the population size
     */
    private void initializePopulation(int populationSize) {
        population = new ArrayList<Individual>(populationSize);

        if (weightGenerator == null) {
            List<double[]> weights = new RandomGenerator(problem.getNumberOfObjectives(), populationSize)
                    .generate();

            for (double[] weight : weights) {
                population.add(new Individual(weight));
            }
        } else {
            List<double[]> weights = weightGenerator.generate();

            if (weights.size() != populationSize) {
                throw new FrameworkException("weight generator must return " + populationSize + " weights");
            }

            for (double[] weight : weights) {
                population.add(new Individual(weight));
            }
        }
    }

    /**
     * Constructs the neighborhoods for all individuals in the population based
     * on the distances between weights.
     */
    private void initializeNeighborhoods() {
        List<Individual> sortedPopulation = new ArrayList<Individual>(population);

        for (Individual individual : population) {
            Collections.sort(sortedPopulation, new WeightSorter(individual));

            for (int i = 0; i < neighborhoodSize; i++) {
                individual.addNeighbor(sortedPopulation.get(i));
            }
        }
    }

    /**
     * Initializes the ideal point.
     */
    private void initializeIdealPoint() {
        idealPoint = new double[problem.getNumberOfObjectives()];
        Arrays.fill(idealPoint, Double.POSITIVE_INFINITY);
    }

    /**
     * Updates the ideal point with the specified solution.
     * 
     * @param solution the solution
     */
    private void updateIdealPoint(Solution solution) {
        for (int i = 0; i < solution.getNumberOfObjectives(); i++) {
            idealPoint[i] = Math.min(idealPoint[i], solution.getObjective(i));
        }
    }

    @Override
    public NondominatedPopulation getResult() {
        NondominatedPopulation result = new NondominatedPopulation();

        if (population != null) {
            for (Individual individual : population) {
                result.add(individual.getSolution());
            }
        }

        return result;
    }

    /**
     * Returns the population indices to be operated on in the current
     * generation.  If the utility update frequency has been set, then this
     * method follows the utility-based MOEA/D search described in [2].
     * Otherwise, this follows the original MOEA/D specification from [1].
     * 
     * @return the population indices to be operated on in the current
     *         generation
     */
    private List<Integer> getSubproblemsToSearch() {
        List<Integer> indices = new ArrayList<Integer>();

        if (updateUtility < 0) {
            // return all indices
            for (int i = 0; i < population.size(); i++) {
                indices.add(i);
            }
        } else {
            // return 1/5 of the indices chosen by their utility
            for (int i = 0; i < problem.getNumberOfObjectives(); i++) {
                indices.add(i);
            }

            for (int i = problem.getNumberOfObjectives(); i < population.size() / 5; i++) {
                int index = PRNG.nextInt(population.size());

                for (int j = 1; j < 10; j++) {
                    int temp = PRNG.nextInt(population.size());

                    if (population.get(temp).getUtility() > population.get(index).getUtility()) {
                        index = temp;
                    }
                }

                indices.add(index);
            }
        }

        PRNG.shuffle(indices);

        return indices;
    }

    /**
     * Returns the population indices to be considered during mating. With
     * probability {@code delta} the neighborhood is returned; otherwise, the
     * entire population is returned.
     * 
     * @param index the index of the first parent
     * @return the population indices to be considered during mating
     */
    private List<Integer> getMatingIndices(int index) {
        List<Integer> matingIndices = new ArrayList<Integer>();

        if (PRNG.nextDouble() <= delta) {
            for (Individual individual : population.get(index).getNeighbors()) {
                matingIndices.add(population.indexOf(individual));
            }
        } else {
            for (int i = 0; i < population.size(); i++) {
                matingIndices.add(i);
            }
        }

        return matingIndices;
    }

    /**
     * Evaluates the fitness of the specified solution using the Chebyshev
     * weights.
     * 
     * @param solution the solution
     * @param weights the weights
     * @return the fitness of the specified solution using the Chebyshev
     *         weights
     */
    private double fitness(Solution solution, double[] weights) {
        double max = Double.NEGATIVE_INFINITY;

        for (int i = 0; i < solution.getNumberOfObjectives(); i++) {
            max = Math.max(max, Math.max(weights[i], 0.0001) * Math.abs(solution.getObjective(i) - idealPoint[i]));
        }

        if (solution.violatesConstraints()) {
            max += 10000.0;
        }

        return max;
    }

    private double sumOfConstraintViolations(Solution solution) {
        double sum = 0.0;

        for (int i = 0; i < solution.getNumberOfConstraints(); i++) {
            sum += Math.abs(solution.getConstraint(i));
        }

        return sum;
    }

    /**
     * Updates the population with the specified solution. Only the specified
     * population indices are considered for updating. A maximum of {@code eta}
     * indices will be modified.
     * 
     * @param solution the solution
     * @param matingIndices the population indices that are available for
     *        updating
     */
    private void updateSolution(Solution solution, List<Integer> matingIndices) {
        int c = 0;
        PRNG.shuffle(matingIndices);

        for (int i = 0; i < matingIndices.size(); i++) {
            Individual individual = population.get(matingIndices.get(i));
            boolean canReplace = false;

            if (solution.violatesConstraints() && individual.getSolution().violatesConstraints()) {
                double cv1 = sumOfConstraintViolations(solution);
                double cv2 = sumOfConstraintViolations(individual.getSolution());

                if (cv1 < cv2) {
                    canReplace = true;
                }
            } else if (individual.getSolution().violatesConstraints()) {
                canReplace = true;
            } else if (solution.violatesConstraints()) {
                // do nothing
            } else {
                if (fitness(solution, individual.getWeights()) < fitness(individual.getSolution(),
                        individual.getWeights())) {
                    canReplace = true;
                }
            }

            if (canReplace) {
                individual.setSolution(solution);
                c = c + 1;
            }

            if (c >= eta) {
                break;
            }
        }
    }

    /**
     * Updates the utility of each individual.
     */
    private void updateUtility() {
        for (Individual individual : population) {
            double oldFitness = individual.getFitness();
            double newFitness = fitness(individual.getSolution(), idealPoint);
            double relativeDecrease = oldFitness - newFitness;

            if (relativeDecrease > 0.001) {
                individual.setUtility(1.0);
            } else {
                double utility = Math.min(1.0, 0.95 * (1.0 + delta / 0.001) * individual.getUtility());
                individual.setUtility(utility);
            }

            individual.setFitness(newFitness);
        }
    }

    @Override
    public void iterate() {
        List<Integer> indices = getSubproblemsToSearch();

        for (Integer index : indices) {
            List<Integer> matingIndices = getMatingIndices(index);

            Solution[] parents = new Solution[variation.getArity()];
            parents[0] = population.get(index).getSolution();

            if (variation.getArity() > 2) {
                // mimic MOEA/D parent selection for differential evolution
                PRNG.shuffle(matingIndices);

                for (int i = 1; i < variation.getArity() - 1; i++) {
                    parents[i] = population.get(matingIndices.get(i - 1)).getSolution();
                }

                parents[variation.getArity() - 1] = population.get(index).getSolution();
            } else {
                for (int i = 1; i < variation.getArity(); i++) {
                    parents[i] = population.get(PRNG.nextItem(matingIndices)).getSolution();
                }
            }

            Solution[] offspring = variation.evolve(parents);

            for (Solution child : offspring) {
                evaluate(child);
                updateIdealPoint(child);
                updateSolution(child, matingIndices);
            }
        }

        generation++;

        if ((updateUtility >= 0) && (generation % updateUtility == 0)) {
            updateUtility();
        }
    }

    /**
     * Proxy for serializing and deserializing the state of a
     * {@code MOEAD} instance. This proxy supports saving
     * the {@code population}, {@code idealPoint} and {@code generation}.
     */
    private static class MOEADState implements Serializable {

        private static final long serialVersionUID = 8694911146929397897L;

        /**
         * The {@code population} from the {@code MOEAD} instance.
         */
        private final List<Individual> population;

        /**
         * The value of the {@code idealPoint} from the {@code MOEAD} instance.
         */
        private final double[] idealPoint;

        /**
         * The value of {@code numberOfEvaluations} from the {@code MOEAD}
         * instance.
         */
        private final int numberOfEvaluations;

        /**
         * The value of {@code generation} from the {@code MOEAD} instance.
         */
        private final int generation;

        /**
         * Constructs a proxy for serializing and deserializing the state of a
         * {@code MOEAD} instance.
         * 
         * @param population the {@code population} from the {@code MOEAD}
         *        instance
         * @param idealPoint the value of the {@code idealPoint} from the
         *        {@code MOEAD} instance
         * @param numberOfEvaluations the value of {@code numberOfEvaluations}
         *        from the {@code MOEAD} instance
         * @param generation the value of {@code generation} from the
         *        {@code MOEAD} instance
         */
        public MOEADState(List<Individual> population, double[] idealPoint, int numberOfEvaluations,
                int generation) {
            super();
            this.population = population;
            this.idealPoint = idealPoint;
            this.numberOfEvaluations = numberOfEvaluations;
            this.generation = generation;
        }

        /**
         * Returns the {@code population} from the {@code MOEAD} instance.
         * 
         * @return the {@code population} from the {@code MOEAD} instance
         */
        public List<Individual> getPopulation() {
            return population;
        }

        /**
         * Returns the value of the {@code idealPoint} from the {@code MOEAD}
         * instance.
         * 
         * @return the value of the {@code idealPoint} from the {@code MOEAD}
         *         instance
         */
        public double[] getIdealPoint() {
            return idealPoint;
        }

        /**
         * Returns the value of {@code numberOfEvaluations} from the
         * {@code MOEAD} instance.
         * 
         * @return the value of {@code numberOfEvaluations} from the
         *         {@code MOEAD} instance
         */
        public int getNumberOfEvaluations() {
            return numberOfEvaluations;
        }

        /**
         * Returns the value of {@code generation} from the {@code MOEAD}
         * instance.
         * 
         * @return the value of {@code generation} from the {@code MOEAD}
         *         instance
         */
        public int getGeneration() {
            return generation;
        }

    }

    @Override
    public Serializable getState() throws NotSerializableException {
        return new MOEADState(population, idealPoint, numberOfEvaluations, generation);
    }

    @Override
    public void setState(Object objState) throws NotSerializableException {
        super.initialize();

        MOEADState state = (MOEADState) objState;

        population = state.getPopulation();
        idealPoint = state.getIdealPoint();
        numberOfEvaluations = state.getNumberOfEvaluations();
        generation = state.getGeneration();
    }

}