// MetricsUtil.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.util;
import jmetal.core.Solution;
import jmetal.core.SolutionSet;
import jmetal.qualityIndicator.Hypervolume;
import jmetal.util.NonDominatedSolutionList;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.*;
/**
* This class provides some utilities to compute quality indicators.
**/
public class MetricsUtil {
/**
* This method reads a Pareto Front for a file.
* @param path The path to the file that contains the pareto front
* @return double [][] whit the pareto front
**/
public double [][] readFront(String path) {
try {
// Open the file
FileInputStream fis = new FileInputStream(path) ;
InputStreamReader isr = new InputStreamReader(fis) ;
BufferedReader br = new BufferedReader(isr) ;
List<double []> list = new ArrayList<double []>();
int numberOfObjectives = 0;
String aux = br.readLine();
while (aux!= null) {
StringTokenizer st = new StringTokenizer(aux);
int i = 0;
numberOfObjectives = st.countTokens();
double [] vector = new double[st.countTokens()];
while (st.hasMoreTokens()) {
double value = new Double(st.nextToken());
vector[i] = value;
i++;
}
list.add(vector);
aux = br.readLine();
}
br.close();
double [][] front = new double[list.size()][numberOfObjectives];
for (int i = 0; i < list.size(); i++) {
front[i] = list.get(i);
}
return front;
} catch (Exception e) {
System.out.println("InputFacilities crashed reading for file: "+path);
e.printStackTrace();
}
return null;
} // readFront
/** Gets the maximum values for each objectives in a given pareto
* front
* @param front The pareto front
* @param noObjectives Number of objectives in the pareto front
* @return double [] An array of noOjectives values whit the maximun values
* for each objective
**/
public double [] getMaximumValues(double [][] front, int noObjectives) {
double [] maximumValue = new double[noObjectives];
for (int i = 0; i < noObjectives; i++)
maximumValue[i] = Double.NEGATIVE_INFINITY;
for (double[] aFront : front) {
for (int j = 0; j < aFront.length; j++) {
if (aFront[j] > maximumValue[j])
maximumValue[j] = aFront[j];
}
}
return maximumValue;
} // getMaximumValues
/** Gets the minimum values for each objectives in a given pareto
* front
* @param front The pareto front
* @param noObjectives Number of objectives in the pareto front
* @return double [] An array of noOjectives values whit the minimum values
* for each objective
**/
public double [] getMinimumValues(double [][] front, int noObjectives) {
double [] minimumValue = new double[noObjectives];
for (int i = 0; i < noObjectives; i++)
minimumValue[i] = Double.MAX_VALUE;
for (double[] aFront : front) {
for (int j = 0; j < aFront.length; j++) {
if (aFront[j] < minimumValue[j])
minimumValue[j] = aFront[j];
}
}
return minimumValue;
} // getMinimumValues
/**
* This method returns the distance (taken the euclidean distance) between
* two points given as <code>double []</code>
* @param a A point
* @param b A point
* @return The euclidean distance between the points
**/
public double distance(double [] a, double [] b) {
double distance = 0.0;
for (int i = 0; i < a.length; i++) {
distance += Math.pow(a[i]-b[i],2.0);
}
return Math.sqrt(distance);
} // distance
/**
* Gets the distance between a point and the nearest one in
* a given front (the front is given as <code>double [][]</code>)
* @param point The point
* @param front The front that contains the other points to calculate the
* distances
* @return The minimun distance between the point and the front
**/
public double distanceToClosedPoint(double [] point, double [][] front) {
double minDistance = distance(point,front[0]);
for (int i = 1; i < front.length; i++) {
double aux = distance(point,front[i]);
if (aux < minDistance) {
minDistance = aux;
}
}
return minDistance;
} // distanceToClosedPoint
/**
* Gets the distance between a point and the nearest one in
* a given front, and this distance is greater than 0.0
* @param point The point
* @param front The front that contains the other points to calculate the
* distances
* @return The minimun distances greater than zero between the point and
* the front
*/
public double distanceToNearestPoint(double [] point, double [][] front) {
double minDistance = Double.MAX_VALUE;
for (double[] aFront : front) {
double aux = distance(point, aFront);
if ((aux < minDistance) && (aux > 0.0)) {
minDistance = aux;
}
}
return minDistance;
} // distanceToNearestPoint
/**
* This method receives a pareto front and two points, one whit maximum values
* and the other with minimum values allowed, and returns a the normalized
* Pareto front.
* @param front A pareto front.
* @param maximumValue The maximum values allowed
* @param minimumValue The minimum values allowed
* @return the normalized pareto front
**/
public double [][] getNormalizedFront(double [][] front,
double [] maximumValue,
double [] minimumValue) {
double [][] normalizedFront = new double[front.length][];
for (int i = 0; i < front.length;i++) {
normalizedFront[i] = new double[front[i].length];
for (int j = 0; j < front[i].length; j++) {
normalizedFront[i][j] = (front[i][j] - minimumValue[j]) /
(maximumValue[j] - minimumValue[j]);
}
}
return normalizedFront;
} // getNormalizedFront
/**
* This method receives a normalized pareto front and return the inverted one.
* This operation needed for minimization problems
* @param front The pareto front to inverse
* @return The inverted pareto front
**/
public double[][] invertedFront(double [][] front) {
double [][] invertedFront = new double[front.length][];
for (int i = 0; i < front.length; i++) {
invertedFront[i] = new double[front[i].length];
for (int j = 0; j < front[i].length; j++) {
if (front[i][j] <= 1.0 && front[i][j]>= 0.0) {
invertedFront[i][j] = 1.0 - front[i][j];
} else if (front[i][j] > 1.0) {
invertedFront[i][j] = 0.0;
} else if (front[i][j] < 0.0) {
invertedFront[i][j] = 1.0;
}
}
}
return invertedFront;
} // invertedFront
/**
* Reads a set of non dominated solutions from a file
* @param path The path of the file containing the data
* @return A solution set
*/
public SolutionSet readSolutionSet(String path) {
try {
/* Open the file */
FileInputStream fis = new FileInputStream(path) ;
InputStreamReader isr = new InputStreamReader(fis) ;
BufferedReader br = new BufferedReader(isr) ;
SolutionSet solutionSet = new SolutionSet();
String aux = br.readLine();
while (aux!= null) {
StringTokenizer st = new StringTokenizer(aux);
int i = 0;
Solution solution = new Solution(st.countTokens());
while (st.hasMoreTokens()) {
double value = new Double(st.nextToken());
solution.setObjective(i,value);
i++;
}
solutionSet.setCapacity(solutionSet.getCapacity()+1);
solutionSet.add(solution);
aux = br.readLine();
}
br.close();
return solutionSet;
} catch (Exception e) {
System.out.println("jmetal.qualityIndicator.util.readNonDominatedSolutionSet: "+path);
e.printStackTrace();
}
return null;
} // readSolutionSet
/**
* Reads a set of non dominated solutions from a file
* @param path The path of the file containing the data
* @return A solution set
*/
public SolutionSet readNonDominatedSolutionSet(String path) {
try {
/* Open the file */
FileInputStream fis = new FileInputStream(path) ;
InputStreamReader isr = new InputStreamReader(fis) ;
BufferedReader br = new BufferedReader(isr) ;
SolutionSet solutionSet = new NonDominatedSolutionList();
String aux = br.readLine();
while (aux!= null) {
StringTokenizer st = new StringTokenizer(aux);
int i = 0;
Solution solution = new Solution(st.countTokens());
while (st.hasMoreTokens()) {
double value = new Double(st.nextToken());
solution.setObjective(i,value);
i++;
}
solutionSet.add(solution);
aux = br.readLine();
}
br.close();
return solutionSet;
} catch (Exception e) {
System.out.println("jmetal.qualityIndicator.util.readNonDominatedSolutionSet: "+path);
e.printStackTrace();
}
return null;
} // readNonDominatedSolutionSet
/**
* Reads a set of non dominated solutions from a file
* and store it in a existing non dominated solution set
* @param path The path of the file containing the data
* @return A solution set
*/
public void readNonDominatedSolutionSet(String path, NonDominatedSolutionList solutionSet) {
try {
/* Open the file */
FileInputStream fis = new FileInputStream(path) ;
InputStreamReader isr = new InputStreamReader(fis) ;
BufferedReader br = new BufferedReader(isr) ;
String aux = br.readLine();
while (aux!= null) {
StringTokenizer st = new StringTokenizer(aux);
int i = 0;
Solution solution = new Solution(st.countTokens());
while (st.hasMoreTokens()) {
double value = new Double(st.nextToken());
solution.setObjective(i,value);
i++;
}
solutionSet.add(solution);
aux = br.readLine();
}
br.close();
} catch (Exception e) {
System.out.println("jmetal.qualityIndicator.util.readNonDominatedSolutionSet: "+path);
e.printStackTrace();
}
}
/**
* Calculates how much hypervolume each point dominates exclusively. The points
* have to be transformed beforehand, to accommodate the assumptions of Zitzler's
* hypervolume code.
* @param front transformed objective values
* @return HV contributions
*/
public double[] hvContributions(int numberOfobjectives, double[][] front) {
Hypervolume hypervolume = new Hypervolume() ;
int numberOfObjectives = numberOfobjectives;
double[] contributions = new double[front.length];
double[][] frontSubset = new double[front.length - 1][front[0].length];
LinkedList<double[]> frontCopy = new LinkedList<double[]>();
Collections.addAll(frontCopy, front);
double[][] totalFront = frontCopy.toArray(frontSubset);
double totalVolume = hypervolume.calculateHypervolume(totalFront, totalFront.length, numberOfObjectives);
for (int i = 0; i < front.length; i++) {
double[] evaluatedPoint = frontCopy.remove(i);
frontSubset = frontCopy.toArray(frontSubset);
// STEP4. The hypervolume (control is passed to java version of Zitzler code)
double hv = hypervolume.calculateHypervolume(frontSubset, frontSubset.length, numberOfObjectives);
double contribution = totalVolume - hv;
contributions[i] = contribution;
// put point back
frontCopy.add(i, evaluatedPoint);
}
return contributions;
}
/**
* Calculates the hv contribution of different populations.
* Receives an array of populations and computes the contribution to HV of the
* population consisting in the union of all of them
* @param populations, consisting in all the populatoins
* @return HV contributions of each population
**/
public double[] hvContributions(SolutionSet [] populations) {
boolean empty = true;
for (SolutionSet population2 : populations)
if (population2.size() > 0)
empty = false;
if (empty) {
double [] contributions = new double[populations.length];
for (int i = 0; i < populations.length;i++)
contributions[i]=0;
for (int i = 0; i < populations.length; i++)
System.out.println(contributions[i]);
return contributions;
}
SolutionSet union;
int size = 0;
double offset_= 0.0;
//determining the global size of the population
for (SolutionSet population1 : populations) size += population1.size();
//allocating space for the union
union = new SolutionSet(size);
// filling union
for (SolutionSet population : populations)
for (int j = 0; j < population.size(); j++)
union.add(population.get(j));
//determining the number of objectives
int numberOfObjectives = union.get(0).getNumberOfObjectives();
//writing everything in matrices
double[][][] frontValues = new double[populations.length+1][][];
frontValues[0] = union.writeObjectivesToMatrix();
for (int i = 0; i < populations.length; i++)
if (populations[i].size() > 0)
frontValues[i+1] = populations[i].writeObjectivesToMatrix();
else
frontValues[i+1] = new double[0][];
// obtain the maximum and minimum values of the Pareto front
double[] maximumValues = getMaximumValues(union.writeObjectivesToMatrix(), numberOfObjectives);
double[] minimumValues = getMinimumValues(union.writeObjectivesToMatrix(), numberOfObjectives);
// normalized all the fronts
double[][][] normalizedFront = new double[populations.length+1][][];
for (int i = 0; i < normalizedFront.length; i++) {
if (frontValues[i].length > 0)
normalizedFront[i] = getNormalizedFront(frontValues[i],maximumValues,minimumValues);
else
normalizedFront[i] = new double[0][];
}
// compute offsets for reference point in normalized space
double[] offsets = new double[maximumValues.length];
for (int i = 0; i < maximumValues.length; i++) {
offsets[i] = offset_ / (maximumValues[i] - minimumValues[i]);
}
//Inverse all the fronts front. This is needed because the original
//metric by Zitzler is for maximization problems
double[][][] invertedFront = new double[populations.length+1][][];
for (int i = 0; i < invertedFront.length; i++)
if (normalizedFront[i].length > 0)
invertedFront[i] = invertedFront(normalizedFront[i]);
else
invertedFront[i] = new double[0][];
// shift away from origin, so that boundary points also get a contribution > 0
for (double[][] anInvertedFront : invertedFront) {
for (double[] point : anInvertedFront) {
for (int i = 0; i < point.length; i++) {
point[i] += offsets[i];
}
}
}
// calculate contributions
double [] contribution = new double[populations.length];
Hypervolume hypervolume = new Hypervolume();
for (int i = 0; i < populations.length;i++) {
if (invertedFront[i+1].length == 0)
contribution[i] = 0;
else
{
if (invertedFront[i+1].length!=invertedFront[0].length) {
double [][] aux = new double[invertedFront[0].length - invertedFront[i+1].length][];
int startPoint = 0, endPoint;
for (int j = 0; j < i; j++) {
startPoint += invertedFront[j+1].length;
}
endPoint = startPoint + invertedFront[i+1].length;
int index = 0;
for (int j = 0; j < invertedFront[0].length; j++) {
if (j < startPoint || j >= (endPoint)) {
aux[index++]= invertedFront[0][j];
}
}
//System.out.println(hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, getNumberOfObjectives));
//System.out.println(hypervolume.calculateHypervolume(aux, aux.length, getNumberOfObjectives));
contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, numberOfObjectives) -
hypervolume.calculateHypervolume(aux, aux.length, numberOfObjectives);
} else {
contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, numberOfObjectives);
}
}
}
//for (int i = 0; i < contribution.length; i++)
//System.out.println(invertedFront[0].length +" "+ invertedFront[i+1].length +" "+ contribution[i]);
return contribution;
}
/**
* Calculates the hv contribution of different populations.
* Receives an array of populations and computes the contribution to HV of the
* population consisting in the union of all of them
* @param populations, consisting in all the populatoins
* @return HV contributions of each population
**/
public double[] hvContributions(SolutionSet archive, SolutionSet [] populations) {
SolutionSet union;
int size = 0;
double offset_= 0.0;
//determining the global size of the population
for (SolutionSet population : populations) size += population.size();
//allocating space for the union
union = archive;
//determining the number of objectives
int numberOfObjectives = union.get(0).getNumberOfObjectives();
//writing everything in matrices
double[][][] frontValues = new double[populations.length+1][][];
frontValues[0] = union.writeObjectivesToMatrix();
for (int i = 0; i < populations.length; i++)
if (populations[i].size() > 0)
frontValues[i+1] = populations[i].writeObjectivesToMatrix();
else
frontValues[i+1] = new double[0][];
// obtain the maximum and minimum values of the Pareto front
double[] maximumValues = getMaximumValues(union.writeObjectivesToMatrix(), numberOfObjectives);
double[] minimumValues = getMinimumValues(union.writeObjectivesToMatrix(), numberOfObjectives);
// normalized all the fronts
double[][][] normalizedFront = new double[populations.length+1][][];
for (int i = 0; i < normalizedFront.length; i++) {
if (frontValues[i].length > 0)
normalizedFront[i] = getNormalizedFront(frontValues[i],maximumValues,minimumValues);
else
normalizedFront[i] = new double[0][];
}
// compute offsets for reference point in normalized space
double[] offsets = new double[maximumValues.length];
for (int i = 0; i < maximumValues.length; i++) {
offsets[i] = offset_ / (maximumValues[i] - minimumValues[i]);
}
//Inverse all the fronts front. This is needed because the original
//metric by Zitzler is for maximization problems
double[][][] invertedFront = new double[populations.length+1][][];
for (int i = 0; i < invertedFront.length; i++)
if (normalizedFront[i].length > 0)
invertedFront[i] = invertedFront(normalizedFront[i]);
else
invertedFront[i] = new double[0][];
// shift away from origin, so that boundary points also get a contribution > 0
for (double[][] anInvertedFront : invertedFront) {
for (double[] point : anInvertedFront) {
for (int i = 0; i < point.length; i++) {
point[i] += offsets[i];
}
}
}
// calculate contributions
double [] contribution = new double[populations.length];
Hypervolume hypervolume = new Hypervolume();
for (int i = 0; i < populations.length;i++) {
if (invertedFront[i+1].length == 0)
contribution[i] = 0;
else
{
int auxSize = 0;
for (int j = 0; j < populations.length; j++) {
if (j!=i)
auxSize += invertedFront[j+1].length;
}
if (size == archive.size()) { // the contribution is the maximum hv
contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, numberOfObjectives);
} else {
//make a front with all the populations but the target one
int index = 0;
double [][] aux = new double[auxSize][];
for (int j = 0; j < populations.length; j++) {
if (j!=i)
for (int k = 0; k < populations[j].size(); k++)
aux[index++] = invertedFront[j+1][k];
}
contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, numberOfObjectives) -
hypervolume.calculateHypervolume(aux, aux.length, numberOfObjectives);
}
/*
int size2 = 0;
for (int j = 0; j < populations.length; j++)
size2+=invertedFront[j+1].length;
double [][] aux = new double[size2 - invertedFront[i+1].length][];
int index = 0;
for (int j = 0; j < populations.length; j++) {
if (j!=i) {
for (int k = 0; k < invertedFront[j+1].length; k++)
aux[index++] = invertedFront[j+1][k];
}
}
System.out.println(hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, getNumberOfObjectives));
System.out.println(index+" "+aux.length);
System.out.println(hypervolume.calculateHypervolume(aux, aux.length, getNumberOfObjectives));
contribution[i] = hypervolume.calculateHypervolume(invertedFront[0], invertedFront[0].length, getNumberOfObjectives) -
hypervolume.calculateHypervolume(aux, aux.length, getNumberOfObjectives);
*/
}
}
//for (int i = 0; i < contribution.length; i++)
//System.out.println(invertedFront[0].length +" "+ invertedFront[i+1].length +" "+ contribution[i]);
return contribution;
}
} // MetricsUtil