/* Copyright 2009-2016 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 org.moeaframework.algorithm; import static org.moeaframework.core.FastNondominatedSorting.RANK_ATTRIBUTE; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.moeaframework.algorithm.single.MinMaxObjectiveComparator; import org.moeaframework.algorithm.single.VectorAngleDistanceScalingComparator; import org.moeaframework.core.PRNG; import org.moeaframework.core.Population; import org.moeaframework.core.Solution; import org.moeaframework.core.comparator.AggregateConstraintComparator; import org.moeaframework.core.comparator.RankComparator; /** * Population implementing the ranking scheme used by the Multiple Single * Objective Pareto Sampling (MSOPS) algorithm. Solutions are ranked with * respect to T weight vectors based on two metrics --- the weighted min-max * aggregate and the vector angle distance scaling (VADS) metric --- producing * 2*T scores/rankings. The overall ranking of a solution is computed using a * lexicographical ordering, where the best rank is given to the solution with * the best individual ranking among the T weight vectors. * <p> * Three methods of constraint handling are described in the literature. In the * original paper, [1], E. Hughes recommends ranking feasible and infeasible * solutions separately and ensuring the ranking if worse for the infeasible * solutions. In the published Matlab code, [2], E. Hughes penalizes the * objective values based on the magnitude of the constraint violation. Lastly, * in [3], E. Hughes states that the infeasible solutions are ranked in order * by their aggregate constraint violation. Regardless of these differences, * all three methods ensure the ranking is always worse for infeasible * solutions. This implementation follows this practice by penalizing the * scores based on the magnitude of the constraint violation (similar to [2]). * <p> * References: * <ol> * <li>E. J. Hughes. "Multiple Single Objective Pareto Sampling." 2003 * Congress on Evolutionary Computation, pp. 2678-2684. * <li>Matlab source code available from * <a href="http://code.evanhughes.org/">http://code.evanhughes.org/</a>. * <li>E. J. Hughes. "MSOPS-II: A general-purpose many-objective optimizer." * 2007 IEEE Congress on Evolutionary Computation, pp. 3944-3951. * <li>E. J. Hughes. "Evolutionary many-objective optimization: many once or * one many." 2005 IEEE Congress on Evolutionary Computation, * pp. 222-227. * </ol> */ public class MSOPSRankedPopulation extends Population { /** * {@code true} if the population has been modified but the ranking method * has not yet been invoked; {@code false} otherwise. */ private boolean modified; /** * The weight vectors. */ private List<double[]> weights; /** * Matrix of scores, updated by calling {@link #update()}. */ double[][] scores; /** * Matrix of rankings, updated by calling {@link #update()}. */ int[][] ranks; /** * Matrix of rankings sorted on each row, updated by calling * {@link #update()}. */ int[][] sortedRanks; /** * Constructs an empty population that maintains the {@code rank} and * attribute for its solutions using the MSOPS ranking method. * * @param weights the weight vectors */ public MSOPSRankedPopulation(List<double[]> weights) { super(); this.weights = weights; } /** * Constructs a population initialized with the specified solutions that * maintains the {@code rank} attribute for its solutions using the MSOPS * ranking method. * * @param weights the weight vectors * @param iterable the solutions used to initialize this population */ public MSOPSRankedPopulation(List<double[]> weights, Iterable<? extends Solution> iterable) { this(weights); addAll(iterable); } @Override public boolean add(Solution solution) { modified = true; return super.add(solution); } @Override public void replace(int index, Solution solution) { modified = true; super.replace(index, solution); } @Override public Solution get(int index) { if (modified) { update(); } return super.get(index); } @Override public void remove(int index) { modified = true; super.remove(index); } @Override public boolean remove(Solution solution) { modified = true; return super.remove(solution); } @Override public void clear() { modified = true; super.clear(); } @Override public Iterator<Solution> iterator() { if (modified) { update(); } return super.iterator(); } @Override public void sort(Comparator<? super Solution> comparator) { if (modified) { update(); } super.sort(comparator); } @Override public void truncate(int size, Comparator<? super Solution> comparator) { if (modified) { update(); } super.truncate(size, comparator); } /** * Equivalent to calling {@code truncate(size, new RankComparator())}. * * @param size the target population size after truncation */ public void truncate(int size) { truncate(size, new RankComparator()); } /** * Returns the neighborhood of solutions nearest to and including the * given solution. * * @param index the index of the solution at the center of the neighborhood * @param size the size of the neighborhood * @return the solutions in the neighborhood */ public Population findNearest(int index, int size) { if (modified) { update(); } int P = size(); int T = weights.size(); // first identify the best/closest weight vectors for this solution List<Integer> bestWeights = new ArrayList<Integer>(); for (int i = 0; i < 2*T; i++) { if (ranks[index][i] == sortedRanks[index][0]) { bestWeights.add(i); } } // randomly select one of these weight vectors final int selectedWeight = PRNG.nextItem(bestWeights); // sort the column to determine which solutions are ranked nearest to // this vector Integer[] indices = new Integer[P]; for (int i = 0; i < P; i++) { indices[i] = i; } Arrays.sort(indices, new Comparator<Integer>() { @Override public int compare(Integer i1, Integer i2) { int rank1 = sortedRanks[i1][selectedWeight]; int rank2 = sortedRanks[i2][selectedWeight]; return rank1 < rank2 ? -1 : rank1 > rank2 ? 1 : 0; } }); // create and return the population of nearest neighbors Population result = new Population(); result.add(get(index)); for (int i = 0; i < size; i++) { if (indices[i] != index) { result.add(get(indices[i])); } } return result; } /** * Updates the rank attribute of all solutions in this population using the * MSOPS ranking method. This method will in general be called * automatically when the population is modified. However, only changes * made to this population can be tracked; changes made directly to the * contained solutions will not be detected. Therefore, it may be necessary * to invoke {@link #update()} manually. */ public void update() { modified = false; final int P = size(); final int T = weights.size(); double maxScore = Double.NEGATIVE_INFINITY; scores = new double[P][2*T]; ranks = new int[P][2*T]; sortedRanks = new int[P][]; // first compute the raw score for (int i = 0; i < P; i++) { Solution solution = get(i); for (int j = 0; j < T; j++) { scores[i][j] = MinMaxObjectiveComparator.calculateFitness(solution, weights.get(j)); scores[i][j+T] = VectorAngleDistanceScalingComparator.calculateFitness(solution, weights.get(j), 100.0); maxScore = Math.max(maxScore, Math.max(scores[i][j], scores[i][j+T])); } } // offset score to handle constraints for (int i = 0; i < P; i++) { Solution solution = get(i); if (solution.violatesConstraints()) { for (int j = 0; j < T; j++) { scores[i][j] = scores[i][j] + maxScore + AggregateConstraintComparator.getConstraints(solution); } } } // convert from raw score to rank for (int i = 0; i < 2*T; i++) { final double[] weightScores = new double[size()]; Integer[] indices = new Integer[size()]; for (int j = 0; j < P; j++) { weightScores[j] = scores[j][i]; indices[j] = j; } Arrays.sort(indices, new Comparator<Integer>() { @Override public int compare(Integer i1, Integer i2) { return Double.compare(weightScores[i1], weightScores[i2]); } }); for (int j = 0; j < P; j++) { ranks[indices[j]][i] = j; } } // sort each row by rank for (int i = 0; i < P; i++) { int[] row = ranks[i].clone(); Arrays.sort(row); sortedRanks[i] = row; } // assign fitness to each individual Integer[] indices = new Integer[P]; for (int i = 0; i < P; i++) { indices[i] = i; } Arrays.sort(indices, new Comparator<Integer>() { @Override public int compare(Integer i1, Integer i2) { int[] ranks1 = sortedRanks[i1]; int[] ranks2 = sortedRanks[i2]; for (int i = 0; i < 2*T; i++) { if (ranks1[i] < ranks2[i]) { return -1; } else if (ranks1[i] > ranks2[i]) { return 1; } } return 0; } }); for (int i = 0; i < P; i++) { get(indices[i]).setAttribute(RANK_ATTRIBUTE, i); } } }