// Hypervolume.java // // Author: // Antonio J. Nebro <antonio@lcc.uma.es> // Juan J. Durillo <durillo@lcc.uma.es> // // Copyright (c) 2011 Antonio J. Nebro, Juan J. Durillo // // This program 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. // // This program 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 this program. If not, see <http://www.gnu.org/licenses/>. package jmetal.qualityIndicator; /** * This class implements the hypervolume indicator. The code is the a Java version * of the original metric implementation by Eckart Zitzler. * It can be used also as a command line program just by typing * $java jmetal.qualityIndicator.Hypervolume <solutionFrontFile> <trueFrontFile> <numberOfOjbectives> * Reference: E. Zitzler and L. Thiele * Multiobjective Evolutionary Algorithms: A Comparative Case Study * and the Strength Pareto Approach, * IEEE Transactions on Evolutionary Computation, vol. 3, no. 4, * pp. 257-271, 1999. */ public class Hypervolume { public jmetal.qualityIndicator.util.MetricsUtil utils_; /** * Constructor * Creates a new instance of MultiDelta */ public Hypervolume() { utils_ = new jmetal.qualityIndicator.util.MetricsUtil(); } // Hypervolume /* returns true if 'point1' dominates 'points2' with respect to the to the first 'noObjectives' objectives */ boolean dominates(double point1[], double point2[], int noObjectives) { int i; int betterInAnyObjective; betterInAnyObjective = 0; for (i = 0; i < noObjectives && point1[i] >= point2[i]; i++) if (point1[i] > point2[i]) betterInAnyObjective = 1; return ((i >= noObjectives) && (betterInAnyObjective>0)); } //Dominates void swap(double [][] front, int i, int j){ double [] temp; temp = front[i]; front[i] = front[j]; front[j] = temp; } // Swap /* all nondominated points regarding the first 'noObjectives' dimensions are collected; the points referenced by 'front[0..noPoints-1]' are considered; 'front' is resorted, such that 'front[0..n-1]' contains the nondominated points; n is returned */ int filterNondominatedSet(double [][] front, int noPoints, int noObjectives){ int i, j; int n; n = noPoints; i = 0; while (i < n) { j = i + 1; while (j < n) { if (dominates(front[i], front[j], noObjectives)) { /* remove point 'j' */ n--; swap(front, j, n); } else if (dominates(front[j], front[i], noObjectives)) { /* remove point 'i'; ensure that the point copied to index 'i' is considered in the next outer loop (thus, decrement i) */ n--; swap(front, i, n); i--; break; } else j++; } i++; } return n; } // FilterNondominatedSet /* calculate next value regarding dimension 'objective'; consider points referenced in 'front[0..noPoints-1]' */ double surfaceUnchangedTo(double [][] front, int noPoints, int objective) { int i; double minValue, value; if (noPoints < 1) System.err.println("run-time error"); minValue = front[0][objective]; for (i = 1; i < noPoints; i++) { value = front[i][objective]; if (value < minValue) minValue = value; } return minValue; } // SurfaceUnchangedTo /* remove all points which have a value <= 'threshold' regarding the dimension 'objective'; the points referenced by 'front[0..noPoints-1]' are considered; 'front' is resorted, such that 'front[0..n-1]' contains the remaining points; 'n' is returned */ int reduceNondominatedSet(double [][] front, int noPoints, int objective, double threshold){ int n; int i; n = noPoints; for (i = 0; i < n; i++) if (front[i][objective] <= threshold) { n--; swap(front, i, n); } return n; } // ReduceNondominatedSet public double calculateHypervolume(double [][] front, int noPoints,int noObjectives){ int n; double volume, distance; volume = 0; distance = 0; n = noPoints; while (n > 0) { int noNondominatedPoints; double tempVolume, tempDistance; noNondominatedPoints = filterNondominatedSet(front, n, noObjectives - 1); //noNondominatedPoints = front.length; if (noObjectives < 3) { if (noNondominatedPoints < 1) System.err.println("run-time error"); tempVolume = front[0][0]; } else tempVolume = calculateHypervolume(front, noNondominatedPoints, noObjectives - 1); tempDistance = surfaceUnchangedTo(front, n, noObjectives - 1); volume += tempVolume * (tempDistance - distance); distance = tempDistance; n = reduceNondominatedSet(front, n, noObjectives - 1, distance); } return volume; } // CalculateHypervolume /* merge two fronts */ double [][] mergeFronts(double [][] front1, int sizeFront1, double [][] front2, int sizeFront2, int noObjectives) { int i, j; int noPoints; double [][] frontPtr; /* allocate memory */ noPoints = sizeFront1 + sizeFront2; frontPtr = new double[noPoints][noObjectives]; /* copy points */ noPoints = 0; for (i = 0; i < sizeFront1; i++) { for (j = 0; j < noObjectives; j++) frontPtr[noPoints][j] = front1[i][j]; noPoints++; } for (i = 0; i < sizeFront2; i++) { for (j = 0; j < noObjectives; j++) frontPtr[noPoints][j] = front2[i][j]; noPoints++; } return frontPtr; } // MergeFronts /** * Returns the hypevolume value of the paretoFront. This method call to the * calculate hipervolume one * @param paretoFront The pareto front * @param paretoTrueFront The true pareto front * @param numberOfObjectives Number of objectives of the pareto front */ public double hypervolume(double [][] paretoFront, double [][] paretoTrueFront, int numberOfObjectives) { /** * Stores the maximum values of true pareto front. */ double[] maximumValues; /** * Stores the minimum values of the true pareto front. */ double[] minimumValues; /** * Stores the normalized front. */ double [][] normalizedFront; /** * Stores the inverted front. Needed for minimization problems */ double [][] invertedFront; // STEP 1. Obtain the maximum and minimum values of the Pareto front maximumValues = utils_.getMaximumValues(paretoTrueFront,numberOfObjectives); minimumValues = utils_.getMinimumValues(paretoTrueFront,numberOfObjectives); // STEP 2. Get the normalized front normalizedFront = utils_.getNormalizedFront(paretoFront, maximumValues, minimumValues); // STEP 3. Inverse the pareto front. This is needed because of the original //metric by Zitzler is for maximization problems invertedFront = utils_.invertedFront(normalizedFront); // STEP4. The hypervolumen (control is passed to java version of Zitzler code) return this.calculateHypervolume(invertedFront,invertedFront.length,numberOfObjectives); }// hypervolume /** * This class can be invoqued from the command line. Three params are required: * 1) the name of the file containing the front, * 2) the name of the file containig the true Pareto front * 3) the number of objectives */ public static void main(String args[]) { if (args.length < 2) { System.err.println("Error using Hypervolume. Usage: \n java jmetal.qualityIndicator.Hypervolume " + "<SolutionFrontFile> " + "<TrueFrontFile> " + "<getNumberOfObjectives>"); System.exit(1); } //Create a new instance of the metric Hypervolume qualityIndicator = new Hypervolume(); //Read the front from the files double [][] solutionFront = qualityIndicator.utils_.readFront(args[0]); double [][] trueFront = qualityIndicator.utils_.readFront(args[1]); //Obtain delta value double value = qualityIndicator.hypervolume(solutionFront, trueFront, new Integer(args[2])); System.out.println(value); } // main } // Hypervolume