/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package hh.moea; import hh.creditassignment.fitnessindicator.DoubleComparator; import hh.creditassignment.fitnessindicator.R2Indicator; import hh.creditassignment.fitnessindicator.SortedLinkedList; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import org.moeaframework.algorithm.AbstractEvolutionaryAlgorithm; import org.moeaframework.core.Initialization; import org.moeaframework.core.NondominatedPopulation; import org.moeaframework.core.ParallelPRNG; import org.moeaframework.core.Population; import org.moeaframework.core.Problem; import org.moeaframework.core.Solution; import org.moeaframework.core.Variation; /** * R2 MOEA is steady state indicator based algorithm developed by Diaz et al. It * is an indicator based algorithm that uses nondominated sorting, then R2 * contribution to achieve a total order of the individuals in the population * * * References: * <p> * <ol> * <li> * "A ranking method based on the R2 indicator for many-objective optimization" * 2013 IEEE Congress on Evolutionary Computation * </ol> * * @author SEAK2 */ public class R2MOEA extends AbstractEvolutionaryAlgorithm { /** * The r2rank comparator. */ private final R2RankComparator rankComparator; /** * The r2contribution comparator. */ private final R2ContributionComparator contributionComparator; private final ArrayList<WtVector> wtVecs; /** * The variation operator. */ private final Variation variation; /** * The utopia point (point not dominated by any individual in the * population) */ private Solution utopia; /** * Only maintain the min objectives and the max objectives */ private Solution minObjs; private Solution maxObjs; /** * stores all the objective values in sorted order to get bounds */ private ArrayList<SortedLinkedList<Double>> sortedObjs; /** * the number of children to create in each generation */ private final int childNumber; /** * parallel purpose random generator */ private final ParallelPRNG pprng; /** * Constructs a new IBEA instance. * * @param problem the problem * @param childNumer the number of children to create in each generation * @param numVecs the number of uniformly spaced vectors to use * @param archive the external archive; or {@code null} if no external * archive is used * @param initialization the initialization operator * @param variation the variation operator */ public R2MOEA(Problem problem, int childNumer, int numVecs, NondominatedPopulation archive, Initialization initialization, Variation variation) { super(problem, new Population(), archive, initialization); this.childNumber = childNumer; this.variation = variation; this.rankComparator = new R2RankComparator(); this.contributionComparator = new R2ContributionComparator(); this.pprng = new ParallelPRNG(); this.wtVecs = initializeWts(problem.getNumberOfObjectives(), numVecs); } @Override protected void initialize() { super.initialize(); computeUtopia(population); computeBounds(population); for (Solution soln : population) { soln.setAttribute("contribution", 0.0); soln.setAttribute("rank", 0); } System.out.println("Finished initializing. Running..."); } @Override protected void iterate() { Population offspring = new Population(); while (offspring.size() < childNumber) { Solution[] parents = selectParents(variation.getArity(), population); Solution[] children = variation.evolve(parents); offspring.addAll(children); } evaluateAll(offspring); population.addAll(offspring); //Update the utopia point with added offspring for (Solution child : offspring) { updateBoundsInsert(child); updateMinMax(); updateUtopia(child); updateMinMax(); } ArrayList<Integer> solutionsToRemove = fastR2Sorting(offspring); for (Integer index : solutionsToRemove) { updateBoundsRemove(population.get(index)); population.remove(index); } } /** * Computes the contribution of a solution to the R2 indicator. Solutions * that do not contribute to the R2 indicator get a higher rank. * * @return the population indices of the solution to remove from the * population */ private ArrayList<Integer> fastR2Sorting(Population offspringPopulation) { ArrayList<Integer> indicesUnrankedSolutions = new ArrayList<>(population.size()); ArrayList<Solution> normalizedPopulation = new ArrayList<>(population.size()); int k=0; for (Solution soln : population) { soln.setAttribute("contribution", 0.0); indicesUnrankedSolutions.add(k); k++; normalizedPopulation.add(normalizeObjectives(soln)); } int rank = 1; while (!indicesUnrankedSolutions.isEmpty()) { HashSet<Integer> contributed = new HashSet(); for (WtVector wt : wtVecs) { double popUtility = Double.POSITIVE_INFINITY; int aIndex = -1; for (int i = 0; i < indicesUnrankedSolutions.size(); i++) { double tmpUtility = solnUtility(wt, normalizedPopulation.get(indicesUnrankedSolutions.get(i)), utopia); if (tmpUtility < popUtility) { popUtility = tmpUtility; aIndex = i; } } Solution a = population.get(indicesUnrankedSolutions.get(aIndex)); a.setAttribute("contribution", popUtility + (double) a.getAttribute("contribution")); contributed.add(aIndex); } //remove from unranked solutions starting from largest index. This will keep unranked solutions ordered ArrayList<Integer> indicesToRemove = new ArrayList<>(contributed); Collections.sort(indicesToRemove); Collections.reverse(indicesToRemove); Iterator iter = indicesToRemove.iterator(); while (iter.hasNext()) { int indexToRemove = (int) iter.next(); Solution soln = population.get(indicesUnrankedSolutions.get(indexToRemove)); soln.setAttribute("rank", rank); indicesUnrankedSolutions.remove(indexToRemove); } rank++; } ArrayList<Integer> leastContributors = new ArrayList<>(); //remove as many individuals as new offspring while (leastContributors.size() < offspringPopulation.size()) { rank--; //find solutions with highest rank ArrayList<Integer> highestRankedSolutions = new ArrayList<>(); for (int i = 0; i < population.size(); i++) { if ((int) population.get(i).getAttribute("rank") == rank) { highestRankedSolutions.add(i); if (leastContributors.size() < offspringPopulation.size()) { return leastContributors; } } } //find solution with highest rank and lowest contribution to R2 value if (highestRankedSolutions.size() == 1) { leastContributors.add(highestRankedSolutions.get(0)); if (leastContributors.size() < offspringPopulation.size()) { return leastContributors; } } else { double minContribution = Double.POSITIVE_INFINITY; for (Integer index : highestRankedSolutions) { double contribution = (double) population.get(index).getAttribute("contribution"); if (contribution < minContribution) { minContribution = contribution; leastContributors.add(index); } } } } return leastContributors; } /** * Computes the utility of a solution wrt to a weight vector using a * Tchebycheff function. Tchebycheff function: u_w(z) = -max{w_j*|z'_j - * z_j|} where w_j is the jth component of the weight vector, z' is the * reference point and z is the objective value. * * @param vec weight vector * @param solution * @param refPt reference point * @return utility of a solution wrt to a weight vectorq */ private double solnUtility(WtVector vec, Solution solution, Solution refPt) { double solnUtil = Double.NEGATIVE_INFINITY; for (int i = 0; i < problem.getNumberOfObjectives(); i++) { solnUtil = Math.max(solnUtil, vec.get(i) * Math.abs(solution.getObjective(i) - refPt.getObjective(i))); } return solnUtil; } private void updateMinMax() { minObjs = problem.newSolution(); maxObjs = problem.newSolution(); for (int i = 0; i < problem.getNumberOfObjectives(); i++) { minObjs.setObjective(i, sortedObjs.get(i).getFirst()); maxObjs.setObjective(i, sortedObjs.get(i).getLast()); } } /** * Finds the utopia point in the population * * @param population the population to find the utopia point * @return the utopia point */ private Solution computeUtopia(Population population) { utopia = problem.newSolution(); for (int i = 0; i < problem.getNumberOfObjectives(); i++) { double min = Double.MAX_VALUE; for (Solution soln : population) { min = Math.min(min, soln.getObjective(i)); } utopia.setObjective(i, min); } return utopia; } /** * Updates the utopia based on the new incoming solution. This method * assumes that the utopia point never deteriorates * * @param offspring new solution entering the population * @return the updated utopia point */ private Solution updateUtopia(Solution offspring) { for (int i = 0; i < offspring.getNumberOfObjectives(); i++) { utopia.setObjective(i, Math.min(utopia.getObjective(i), offspring.getObjective(i))); } return utopia; } /** * Normalizes the objective vector of a given individual and copies over * rank and contribution attributes * * @param solution * @return */ private Solution normalizeObjectives(Solution solution) { Solution normalizedObjs = solution.copy(); for (int i = 0; i < solution.getNumberOfObjectives(); i++) { double lowBound = minObjs.getObjective(i); double upBound = maxObjs.getObjective(i); normalizedObjs.setObjective(i, (solution.getObjective(i) - lowBound) / (upBound - lowBound)); } return normalizedObjs; } //Computes the bounds on population private void computeBounds(Population pop) { sortedObjs = new ArrayList<>(pop.get(0).getNumberOfObjectives()); for (int i = 0; i < pop.get(0).getNumberOfObjectives(); i++) { ArrayList<Double> objs = new ArrayList(pop.size()); for (Solution soln : pop) { objs.add(soln.getObjective(i)); } sortedObjs.add(new SortedLinkedList<>(objs, new DoubleComparator())); } } //updates the bounds based on a solution exiting the population private void updateBoundsRemove(Solution removedSoln) { for (int i = 0; i < removedSoln.getNumberOfObjectives(); i++) { int index = sortedObjs.get(i).binaryFind(removedSoln.getObjective(i)); sortedObjs.get(i).remove(index); } } //updates the bounds based on a solution exiting the population private void updateBoundsInsert(Solution newSolution) { for (int i = 0; i < newSolution.getNumberOfObjectives(); i++) { sortedObjs.get(i).add(newSolution.getObjective(i)); } } /** * This method is a binary tournament selection of parents based on the * fitness values (i.e. R2 contribution rank) * * @param arity * @param population * @return */ private Solution[] selectParents(int arity, Population population) { Solution[] result = new Solution[arity]; for (int i = 0; i < arity; i++) { Solution winner = population.get(pprng.nextInt(population.size())); for (int j = 1; j < 2; j++) { Solution candidate = population .get(pprng.nextInt(population.size())); int flag = rankComparator.compare(winner, candidate); //first compare using rank. then use contribution if (flag > 0) { winner = candidate; } else if (flag == 0) { flag = contributionComparator.compare(winner, candidate); if (flag > 0) { winner = candidate; } } } result[i] = winner; } return result; } /** * This comparator compares the R2 rank computed from fast R2 sorting */ private class R2RankComparator implements Comparator<Solution> { @Override public int compare(Solution t, Solution t1) { int rank1 = (int) t.getAttribute("rank"); int rank2 = (int) t.getAttribute("rank"); return Integer.compare(rank1, rank2); } } /** * This comparator compares the R2 contribution */ private class R2ContributionComparator implements Comparator<Solution> { @Override public int compare(Solution t, Solution t1) { double ca1 = (double) t.getAttribute("contribution"); double ca2 = (double) t.getAttribute("contribution"); return Double.compare(ca1, ca2); } } /** * Method from jmetal to load the weights for problems meeting certain * criteria such as number of objectives and population size. Returns true * if the weights can be loaded and false if the weights data is * unavailable. * * @param numObj * @param numVecs */ private ArrayList<WtVector> initializeWts(int numObj, int numVecs) { String dataFileName; dataFileName = "W" + numObj + "D_" + numVecs + ".dat"; ArrayList<WtVector> out = new ArrayList<>(numVecs); try { // Open the file FileInputStream fis = new FileInputStream("weight" + File.separator + dataFileName); InputStreamReader isr = new InputStreamReader(fis); BufferedReader br = new BufferedReader(isr); int j = 0; String aux = br.readLine(); while (aux != null) { StringTokenizer st = new StringTokenizer(aux); j = 0; double[] wts = new double[numObj]; while (st.hasMoreTokens()) { double value = (new Double(st.nextToken())).doubleValue(); wts[j] = value; j++; } out.add(new WtVector(wts)); aux = br.readLine(); } br.close(); } catch (IOException | NumberFormatException e) { System.out .println("initUniformWeight: failed when reading for file: " + "weight" + File.separator + dataFileName); Logger.getLogger(R2Indicator.class.getName()).log(Level.SEVERE, null, e); } return out; } protected class WtVector { /** * Weights for vector */ private final double[] weights; public WtVector(double[] weights) { this.weights = weights; } public double get(int i) { return weights[i]; } } }