/* * DifferentialEvolution.java * * Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard * * This file is part of BEAST. * See the NOTICE file distributed with this work for additional * information regarding copyright ownership and licensing. * * BEAST 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 2 * of the License, or (at your option) any later version. * * BEAST 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 BEAST; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ package dr.math; /** * <b>global</b> minimization of a real-valued function of several * variables without using derivatives using a genetic algorithm * Price, K., and R. Storn. 1997. Differential evolution: a simple * strategy for fast optimization. Dr. Dobb's Journal 264(April), pp. 18-24. * Strategy used here: DE/rand-to-best/1/bin * @author Korbinian Strimmer */ public class DifferentialEvolution extends MultivariateMinimum { // // Public stuff // // Variables that control aspects of the inner workings of the // minimization algorithm. Setting them is optional, they // are all set to some reasonable default values given below. /** weight factor (default 0.7) */ public double F = 0.7 /* 0.5*/; /** Crossing over factor (default 0.9) */ public double CR = 0.9 /*1.0*/; /** * variable controlling print out, default value = 0 * (0 -> no output, 1 -> print final value, * 2 -> detailed map of optimization process) */ public int prin = 0; /** * construct DE optimization modul (population size is * selected automatically) * * <p><em>DE web page:</em> * <a href="http://www.icsi.berkeley.edu/~storn/code.html" * >http://www.icsi.berkeley.edu/~storn/code.html</a> * * @param dim dimension of optimization vector */ public DifferentialEvolution (int dim) { this(dim, 5*dim); } /** * construct optimization modul * * @param dim dimension of optimization vector * @param popSize population size */ public DifferentialEvolution (int dim, int popSize) { // Dimension and Population size dimension = dim; populationSize = popSize; numFun = 0; // Allocate memory currentPopulation = new double[populationSize][dimension]; nextPopulation = new double[populationSize][dimension]; costs = new double[populationSize]; trialVector = new double[dimension]; // helper variable //numr = 5; // for strategy DE/best/2/bin numr = 3; // for stragey DE/rand-to-best/1/bin r = new int[numr]; } // implementation of abstract method public void optimize(MultivariateFunction func, double[] xvec, double tolfx, double tolx) { optimize(func,xvec,tolfx,tolx,null); } public void optimize(MultivariateFunction func, double[] xvec, double tolfx, double tolx, MinimiserMonitor monitor) { f = func; x = xvec; // Create first generation firstGeneration (); stopCondition(fx, x, tolfx, tolx, true); while (true) { boolean xHasChanged; do { xHasChanged = nextGeneration (); if (maxFun > 0 && numFun > maxFun) { break; } if (prin > 1 && currGen % 20 == 0) { printStatistics(); } if(monitor!=null) { monitor.newMinimum(fx,xvec); } } while (!xHasChanged); if (stopCondition(fx, x, tolfx, tolx, false) || (maxFun > 0 && numFun > maxFun)) { break; } } if (prin > 0) printStatistics(); } // // Private stuff // private MultivariateFunction f; private int currGen; private double fx; private double[] x; // Dimension private int dimension; // Population size private int populationSize; // Population data private double trialCost; private double[] costs; private double[] trialVector; private double[][] currentPopulation; private double[][] nextPopulation; // Helper variable private int numr; private int[] r; private void printStatistics() { // Compute mean double meanCost = 0.0; for (int i = 0; i < populationSize; i++) { meanCost += costs[i]; } meanCost = meanCost/populationSize; // Compute variance double varCost = 0.0; for (int i = 0; i < populationSize; i++) { double tmp = (costs[i]-meanCost); varCost += tmp*tmp; } varCost = varCost/(populationSize-1); System.out.println(); System.out.println(); System.out.println(); System.out.println("Smallest value: " + fx); System.out.println(); for (int k = 0; k < dimension; k++) { System.out.println("x[" + k + "] = " + x[k]); } System.out.println(); System.out.println("Current Generation: " + currGen); System.out.println("Function evaluations: " + numFun); System.out.println("Populations size (populationSize): " + populationSize); System.out.println("Average value: " + meanCost); System.out.println("Variance: " + varCost); System.out.println("Weight factor (F): " + F); System.out.println("Crossing-over (CR): " + CR); System.out.println(); } // Generate starting population private void firstGeneration() { currGen = 1; // Construct populationSize random start vectors for (int i = 0; i < populationSize; i++) { for (int j = 0; j < dimension; j++ ) { double min = f.getLowerBound(j); double max = f.getUpperBound(j); double diff = max - min; // Uniformly distributed sample points currentPopulation[i][j] = min + diff* MathUtils.nextDouble(); } costs[i] = f.evaluate(currentPopulation[i]); } numFun += populationSize; findSmallestCost (); } // check whether a parameter is out of range private double checkBounds(double param, int numParam) { if (param < f.getLowerBound(numParam)) { return f.getLowerBound(numParam); } else if (param > f.getUpperBound(numParam)) { return f.getUpperBound(numParam); } else { return param; } } // Generate next generation private boolean nextGeneration() { boolean updateFlag = false; int best = 0; // to avoid compiler complaints double[][] swap; currGen++; // Loop through all population vectors for (int r0 = 0; r0 < populationSize; r0++) { // Choose ri so that r0 != r[1] != r[2] != r[3] != r[4] ... r[0] = r0; for (int k = 1; k < numr; k++) { r[k] = randomInteger (populationSize-k); for (int l = 0; l < k; l++) { if (r[k] >= r[l]) { r[k]++; } } } copy(trialVector, currentPopulation[r0]); int n = randomInteger (dimension); for (int i = 0; i < dimension; i++) // perform binomial trials { // change at least one parameter if (MathUtils.nextDouble() < CR || i == dimension - 1) { // DE/rand-to-best/1/bin // (change to 'numr=3' in constructor when using this strategy) trialVector[n] = trialVector[n] + F*(x[n] - trialVector[n]) + F*(currentPopulation[r[1]][n] - currentPopulation[r[2]][n]); //DE/rand-to-best/2/bin //double K = rng.nextDouble(); //trialVector[n] = trialVector[n] + // K*(x[n] - trialVector[n]) + // F*(currentPopulation[r[1]][n] - currentPopulation[r[2]][n]); // DE/best/2/bin // (change to 'numr=5' in constructor when using this strategy) //trialVector[n] = x[n] + // (currentPopulation[r[1]][n]+currentPopulation[r[2]][n] // -currentPopulation[r[3]][n]-currentPopulation[r[4]][n])*F; } n = (n+1) % dimension; } // make sure that trial vector obeys boundaries for (int i = 0; i < dimension; i++) { trialVector[i] = checkBounds(trialVector[i], i); } // Test this choice trialCost = f.evaluate(trialVector); if (trialCost < costs[r0]) { // Better than old vector costs[r0] = trialCost; copy(nextPopulation[r0], trialVector); // Check for new best vector if (trialCost < fx) { fx = trialCost; best = r0; updateFlag = true; } } else { // Keep old vector copy(nextPopulation[r0], currentPopulation[r0]); } } numFun += populationSize; // Update best vector if (updateFlag) { copy(x, nextPopulation[best]); } // Switch pointers swap = currentPopulation; currentPopulation = nextPopulation; nextPopulation = swap; return updateFlag; } // Determine vector with smallest cost in current population private void findSmallestCost() { int best = 0; fx = costs[0]; for (int i = 1; i < populationSize; i++) { if (costs[i] < fx) { fx = costs[i]; best = i; } } copy(x, currentPopulation[best]); } // draw random integer in the range from 0 to n-1 private int randomInteger(int n) { return MathUtils.nextInt(n); } }