/* 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.util.weights; import java.util.ArrayList; import java.util.List; import org.apache.commons.math3.util.CombinatoricsUtils; /** * Generates weights using the Normal Boundary Intersection (NBI) method. * For {@code d} divisions and {@code M} objectives, this class will generate * {@code M+d-1 choose d} weights. * <p> * References: * <ol> * <li>Das, I. and J. Dennis (1998). "Normal-boundary intersection: A new * method for generating the Pareto surface in nonlinear multicriteria * optimization problems." SIAM J. Optimization, 8(3):631-657. * <li>Deb, K. and H. Jain (2014). "An Evolutionary Many-Objective * Optimization Algorithm Using Reference-Point-Based Nondominated * Sorting Approach, Part I: Solving Problems With Box Constraints." * IEEE Transactions on Evolutionary Computation, 18(4):577-601. * </ol> */ public class NormalBoundaryIntersectionGenerator implements WeightGenerator { /** * The number of objectives. */ private final int numberOfObjectives; /** * The number of outer divisions. */ private final int divisionsOuter; /** * The number of inner divisions, or {@code 0} if no inner divisions should * be used. */ private final int divisionsInner; /** * Constructs a new normal-boundary intersection weight generator. * * @param numberOfObjectives the number of objectives * @param divisions the number of divisions */ public NormalBoundaryIntersectionGenerator(int numberOfObjectives, int divisions) { this(numberOfObjectives, divisions, 0); } /** * Constructs a new normal-boundary intersection weight generator using the * two-layer approach of Deb and Jain (2014). * * @param numberOfObjectives the number of objectives * @param divisionsOuter the number of outer divisions * @param divisionsInner the number of inner divisions */ public NormalBoundaryIntersectionGenerator(int numberOfObjectives, int divisionsOuter, int divisionsInner) { super(); this.numberOfObjectives = numberOfObjectives; this.divisionsOuter = divisionsOuter; this.divisionsInner = divisionsInner; } @Override public int size() { long size = 0; size += CombinatoricsUtils.binomialCoefficient( numberOfObjectives + divisionsOuter - 1, divisionsOuter); if (divisionsInner > 0) { size += CombinatoricsUtils.binomialCoefficient( numberOfObjectives + divisionsInner - 1, divisionsInner); } return (int)size; } @Override public List<double[]> generate() { List<double[]> weights = null; if (divisionsInner > 0) { if (divisionsOuter >= numberOfObjectives) { System.err.println("The specified number of outer divisions produces intermediate reference points, recommend setting divisionsOuter < numberOfObjectives."); } weights = generateWeights(divisionsOuter); // offset the inner weights List<double[]> inner = generateWeights(divisionsInner); for (int i = 0; i < inner.size(); i++) { double[] weight = inner.get(i); for (int j = 0; j < weight.length; j++) { weight[j] = (1.0/numberOfObjectives + weight[j])/2; } } weights.addAll(inner); } else { if (divisionsOuter < numberOfObjectives) { System.err.println("No intermediate reference points will be generated for the specified number of divisions, recommend increasing divisions"); } weights = generateWeights(divisionsOuter); } return weights; } /** * Generates the reference points (weights) for the given number of * divisions. * * @param divisions the number of divisions * @return the list of reference points */ private List<double[]> generateWeights(int divisions) { List<double[]> result = new ArrayList<double[]>(); double[] weight = new double[numberOfObjectives]; generateRecursive(result, weight, numberOfObjectives, divisions, divisions, 0); return result; } /** * Generate reference points (weights) recursively. * * @param weights list storing the generated reference points * @param weight the partial reference point being recursively generated * @param numberOfObjectives the number of objectives * @param left the number of remaining divisions * @param total the total number of divisions * @param index the current index being generated */ private void generateRecursive(List<double[]> weights, double[] weight, int numberOfObjectives, int left, int total, int index) { if (index == (numberOfObjectives - 1)) { weight[index] = (double)left/total; weights.add(weight.clone()); } else { for (int i = 0; i <= left; i += 1) { weight[index] = (double) i / total; generateRecursive(weights, weight, numberOfObjectives, left - i, total, index + 1); } } } }