Java tutorial
/* * Copyright 2015 Vehbi Sinan Tunalioglu <vst@vsthost.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.vsthost.rnd.jdeoptim; import com.vsthost.rnd.jdeoptim.evolution.Objective; import org.apache.commons.math3.distribution.UniformIntegerDistribution; import org.apache.commons.math3.distribution.UniformRealDistribution; import org.apache.commons.math3.random.MersenneTwister; import org.apache.commons.math3.random.RandomGenerator; import java.util.Arrays; /** * Defines a differential evolution algorithm class. * * @author Vehbi Sinan Tunalioglu */ @Deprecated public class DE { /** * Defines the objective function to be used. */ private Objective function; /** * Defines the dimension of the problem. */ private int dimension; /** * Defines the lower limits of the values to be optimized. */ private double[] lowerLimit; /** * Defines the upper limits of the values to be optimized. */ private double[] upperLimit; /** * Defines the size of the population. */ private int populationSize; /** * Defines the population. */ private double[][] population; /** * Defines the number of iterations: */ private int iterations; /** * Defines the crossover probability. */ private double crossoverProbability; /** * Defines the weight. */ private double weight; /** * Defines the best candidate. */ private double[] bestCandidate; /** * Defines the best candidate score. */ private double bestScore = Double.POSITIVE_INFINITY; /** * Defines an array of scores matching the active population. */ private double[] scores; /** * Indicates if we are logging iteration diagnostics. */ private boolean logging = true; /** * Defines the iteration log format. */ private String iterationLogFormat; /** * Defines the start timestamp of the run. */ private long timeStarted; /** * Defines the finish timestamp of the run. */ private long timeFinished; /** * Defines the random number generator. */ private RandomGenerator randomGenerator = new MersenneTwister(); /** * Constructor for the differential evolution algorithm runner. * * @param function The objective function * @param lowerLimit Lower limits * @param upperLimit Upper limits * @param iterations The number of iterations * @param populationSize The size of the population * @param crossoverProbability The crossover probability * @param weight The weight for deviations * @param logging Flag indivating if we are logging or not */ public DE(Objective function, double[] lowerLimit, double[] upperLimit, int iterations, int populationSize, double crossoverProbability, double weight, boolean logging) { // Set the function: this.function = function; // Check upper/lower limit lengths: if (lowerLimit.length == 0 || lowerLimit.length != upperLimit.length) { throw new RuntimeException("Limit lengths should match each other and must be bigger than 0."); } // Check if lower limits are less than or equal to upper limits: for (int i = 0; i < lowerLimit.length; i++) { if (lowerLimit[i] > upperLimit[i]) { throw new RuntimeException("Lower limit cannot be greater than the corresponding upper limit."); } } // Set limits: this.lowerLimit = lowerLimit; this.upperLimit = upperLimit; // Set the dimension: this.dimension = this.lowerLimit.length; // Set the population size: this.populationSize = populationSize; // Set the number of iterations: this.iterations = iterations; // Set the crossover probability: this.crossoverProbability = crossoverProbability; // Set the weight: this.weight = weight; // Set the logging flag: this.logging = logging; // Initialize: this.initialize(); } /** * Constructor for the differential evolution algorithm runner. * * @param function The objective function * @param lowerLimit Lower limits * @param upperLimit Upper limits * @param iterations The number of iterations * @param initialPopulation Initial population * @param crossoverProbability The crossover probability * @param weight The weight for deviations * @param logging Flag indivating if we are logging or not */ public DE(Objective function, double[] lowerLimit, double[] upperLimit, int iterations, double[][] initialPopulation, double crossoverProbability, double weight, boolean logging) { // Set the function: this.function = function; // Check upper/lower limit lengths: if (lowerLimit.length == 0 || lowerLimit.length != upperLimit.length) { throw new RuntimeException("Limit lengths should match each other and must be bigger than 0."); } // Check if lower limits are less than or equal to upper limits: for (int i = 0; i < lowerLimit.length; i++) { if (lowerLimit[i] > upperLimit[i]) { throw new RuntimeException("Lower limit cannot be greater than the corresponding upper limit."); } } // Set limits: this.lowerLimit = lowerLimit; this.upperLimit = upperLimit; // Set the dimension: this.dimension = this.lowerLimit.length; // Set the population: this.population = initialPopulation; // Set the population size: this.populationSize = initialPopulation.length; // Set the number of iterations: this.iterations = iterations; // Set the crossover probability: this.crossoverProbability = crossoverProbability; // Set the weight: this.weight = weight; // Set the logging flag: this.logging = logging; // Initialize: this.initialize(); } public RandomGenerator getRandomGenerator() { return randomGenerator; } public void setRandomGenerator(RandomGenerator randomGenerator) { this.randomGenerator = randomGenerator; } /** * Initializes the evolution algorithm. */ private void initialize() { // Initialize the iteration log format: this.iterationLogFormat = "Iteration: %0" + String.valueOf(this.iterations).length() + "d [%.6f] %s"; } /** * Initializes the population. */ private void initializePopulation() { System.out.println("Generating population..."); // Initialize the population: this.population = new double[this.populationSize][dimension]; // Iterate over population size and populate candidate: for (int c = 0; c < this.populationSize; c++) { // Iterate over dimensions and populate candidate elements: for (int d = 0; d < this.dimension; d++) { // Get the minimum possible value: final double min = this.lowerLimit[d]; // Get the maximum possible value: final double max = this.upperLimit[d]; // Create a random value within the range: // TODO: Use a better random number generator? this.population[c][d] = min + ((max - min) * Math.random()); } } } /** * Prints a line of information for debugging and narrative purposes. * * @param iteration The iteration number. */ public void log(int iteration) { // Are we logging? if (!this.logging) { // Nope, return immediately: return; } // Print the line: System.out.println(String.format(this.iterationLogFormat, iteration, this.bestScore, DE.candidateToString(this.getBestCandidate()))); } /** * Computes and returns a new population. * * @return A new population. */ private void generatePopulation() { // Declare and initialize the new population: double[][] newPopulation = new double[this.populationSize][]; // Declare and initialize the new population scores: double[] newScores = new double[this.populationSize]; // Define a uniform distribution: UniformIntegerDistribution distributionForCol = new UniformIntegerDistribution(this.randomGenerator, 0, this.dimension - 1); UniformIntegerDistribution distributionForRow = new UniformIntegerDistribution(this.randomGenerator, 0, this.populationSize - 1); UniformRealDistribution distributionForCO = new UniformRealDistribution(this.randomGenerator, 0, 1); // Iterate over the current population: for (int c = 0; c < this.populationSize; c++) { // Get the candidate as the base of the next candidate (a.k.a. trial): double[] trial = Arrays.copyOf(this.population[c], this.dimension); // TODO: Implement all strategies. // Pick a random index from the dimension to start with: int index = distributionForCol.sample(); // Get 2 elements from the population which are distinct: int r1 = -1, r2 = -1; while (c == r1 || c == r2 || r1 == r2) { r1 = distributionForRow.sample(); r2 = distributionForRow.sample(); } // Iterate over dimensions and do stuff: for (int _i = 0; _i < this.dimension; _i++) { // To crossover or not to crossover: if (distributionForCO.sample() < this.crossoverProbability) { // Using strategy "DE/best/1/bin with jitter" as in R's DEoptim package (strategy==3): trial[index] = this.bestCandidate[index] + this.weight * (distributionForCO.sample() + 0.0001) * (this.population[r1][index] - this.population[r2][index]); // Using strategy "DE/local-to-best/1/bin" as in R's DEoptim package (strategy==2): // trial[index] += this.weight * (this.population[r1][index] - this.population[r2][index]) + this.weight * (this.bestCandidate[index] - trial[index]); } // Update index: index = (index + 1) % dimension; } // // Apply limits with reflection: // for (int i = 0; i < this.lowerLimit.length; i++) { // // Check lower limit: // if (trial[i] < this.lowerLimit[i]) { // trial[i] = this.lowerLimit[i] + trial[i]; // } // // Check upper limit: // else if (trial[i] > this.upperLimit[i]) { // trial[i] = this.upperLimit[i] - trial[i]; // } // } // Apply limits: for (int i = 0; i < this.lowerLimit.length; i++) { // Check lower limit: if (trial[i] < this.lowerLimit[i]) { trial[i] = this.lowerLimit[i]; } // Check upper limit: else if (trial[i] > this.upperLimit[i]) { trial[i] = this.upperLimit[i]; } } // Compute the score of the trial: final double score = this.function.apply(trial); // Check the score of this candidate: if (score < this.scores[c]) { newPopulation[c] = trial; newScores[c] = score; } else { newPopulation[c] = this.population[c]; newScores[c] = this.scores[c]; } } // Now, reset the population and scores: this.population = newPopulation; this.scores = newScores; } /** * Runs the evolutionary algorithm and sets the best candidate. */ public void run() { // Mark the start time: this.timeStarted = System.currentTimeMillis(); // Initialize the first population: if (this.population == null) { this.initializePopulation(); } // Initialize the array of scores to be used at each iteration with the current population: this.scores = new double[this.populationSize]; for (int i = 0; i < this.populationSize; i++) { this.scores[i] = this.function.apply(this.population[i]); } // Get the best score index and update best candidate and its score: int bestIndex = DE.minIndex(scores); this.bestCandidate = Arrays.copyOf(this.population[bestIndex], this.dimension); this.bestScore = this.scores[bestIndex]; // Iterate over number of iterations: for (int iteration = 1; iteration < this.iterations; iteration++) { // Generate the new population, update scores etc.: this.generatePopulation(); // Compute the index of the best candidate: bestIndex = DE.minIndex(this.scores); // Choose if the score is better: if (this.scores[bestIndex] < this.bestScore) { this.bestCandidate = Arrays.copyOf(this.population[bestIndex], this.dimension); this.bestScore = this.scores[bestIndex]; } // Narrate: this.log(iteration); } // Mark the finish timestamp: this.timeFinished = System.currentTimeMillis(); } /** * Returns the best candidate evolved. * * @return The best candidate. */ public double[] getBestCandidate() { return this.bestCandidate; } /** * Prints a diagnosis to the standard output. */ public void diagnose() { System.out.println("Best Candidate : " + DE.candidateToString(this.getBestCandidate())); System.out.println("Best Score : " + this.function.apply(this.getBestCandidate())); System.out.println("Total Runtime : " + (this.timeFinished - this.timeStarted) + " msecs."); } /** * Returns the candidate as a string. * * @param candidate The candidate to be formatted. * @return The string representation of the candidate. */ private static String candidateToString(double[] candidate) { return Arrays.toString(candidate); } /** * Returns the index of the minimum element in the double array provided. * * @param array The array to be search for the index of the minimum element. * @return The index of the minimum element in the array. */ private static int minIndex(double[] array) { // Declare and initialize the return value: int index = -1; // Declare and initialize the minimum value: double min = Double.POSITIVE_INFINITY; // Find the best score and update values: for (int i = 0; i < array.length; i++) { if (array[i] <= min) { min = array[i]; index = i; } } // Done, return: return index; } /** * Returns the best score. * * @return The best score. */ public double getBestScore() { return bestScore; } }