All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.evosuite.ga.metaheuristics.SPEA2 Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * This file is part of EvoSuite.
 *
 * EvoSuite 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.0 of the License, or
 * (at your option) any later version.
 *
 * EvoSuite 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 Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see .
 */
package org.evosuite.ga.metaheuristics;

import org.apache.commons.lang3.tuple.Pair;
import org.evosuite.Properties;
import org.evosuite.ga.Chromosome;
import org.evosuite.ga.ChromosomeFactory;
import org.evosuite.ga.ConstructionFailedException;
import org.evosuite.ga.FitnessFunction;
import org.evosuite.ga.comparators.DominanceComparator;
import org.evosuite.ga.comparators.StrengthFitnessComparator;
import org.evosuite.utils.Randomness;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 * SPEA2 implementation.
 * 
 * @techreport{ZLT:2001,
            author = {E. Zitzler and M. Laumanns and L. Thiele},
            title = {{SPEA2: Improving the Strength Pareto Evolutionary Algorithm}},
            institution = {Computer Engineering and Networks Laboratory (TIK), Swiss Federal
            Institute of Technology (ETH), Zurich, Switzerland},
            year = {2001},
            number = {103}}
 *
 * @author José Campos
 */
public class SPEA2 extends GeneticAlgorithm {

  private static final long serialVersionUID = -7638497183625040479L;

  private static final Logger logger = LoggerFactory.getLogger(SPEA2.class);

  private DominanceComparator comparator;

  // TODO should we use 'archive' from GeneticAlgorithm class?
  private List archive = null;

  public SPEA2(ChromosomeFactory factory) {
    super(factory);
    this.comparator = new DominanceComparator();
  }

  @SuppressWarnings("unchecked")
  @Override
  protected void evolve() {
    /*
     * Reproduction
     */

    List offspringPopulation = new ArrayList(Properties.POPULATION);
    while (offspringPopulation.size() < Properties.POPULATION) {
      // TODO SelectionFunction has to be a BinaryTournamentSelection, i.e.,
      // a TournamenteSelection with 2 tournaments
      // TODO we might want to use BinaryTournamentSelectionCrowdedComparison
      T parent1 = this.selectionFunction.select(this.archive);
      T parent2 = this.selectionFunction.select(this.archive);

      T offspring1 = (T) parent1.clone();
      T offspring2 = (T) parent2.clone();

      if (Randomness.nextDouble() <= Properties.CROSSOVER_RATE) {
        try {
          this.crossoverFunction.crossOver(offspring1, offspring2);
        } catch (ConstructionFailedException e) {
          logger.error("Crossover failed: " + e.getMessage());
          e.printStackTrace();
        }
      }

      if (Randomness.nextDouble() <= Properties.MUTATION_RATE) {
        this.notifyMutation(offspring1);
        offspring1.mutate();
        this.notifyMutation(offspring2);
        offspring2.mutate();
      }

      offspringPopulation.add(offspring1);
      offspringPopulation.add(offspring2);
    }

    /*
     * Evaluation
     */

    for (T element : offspringPopulation) {
      for (final FitnessFunction ff : this.getFitnessFunctions()) {
        ff.getFitness(element);
        notifyEvaluation(element);
      }
    }

    /*
     * Replacement
     */

    this.population.clear();
    this.population.addAll(offspringPopulation);

    this.currentIteration++;
  }

  @Override
  public void initializePopulation() {
    this.notifySearchStarted();
    this.currentIteration = 0;

    // Generate an initial population P0
    this.generateInitialPopulation(Properties.POPULATION);
    // and create an empty archive of the same size
    this.archive = new ArrayList(Properties.POPULATION);

    for (T element : this.population) {
      for (final FitnessFunction ff : this.getFitnessFunctions()) {
        ff.getFitness(element);
        notifyEvaluation(element);
      }
    }
    this.updateArchive();
    this.writeIndividuals(this.archive);

    this.notifyIteration();
  }

  @Override
  public void generateSolution() {
    if (this.population.isEmpty()) {
      this.initializePopulation();
    }

    while (!isFinished()) {
      this.evolve();
      this.updateArchive();
      this.notifyIteration();
      this.writeIndividuals(this.archive);
    }

    // replace population object with archive, so that when 'getBestIndividuals()'
    // function is called, the correct list of solutions is returned
    this.population = this.archive;

    this.notifySearchFinished();
  }

  private void updateArchive() {
    List union = new ArrayList(2 * Properties.POPULATION);
    union.addAll(population);
    union.addAll(this.archive);
    this.computeStrength(union);
    this.archive = this.environmentalSelection(union);
  }

  /**
   * 
   * @param population
   * @return
   */
  protected List environmentalSelection(List union) {

    List populationCopy = new ArrayList(union.size());
    populationCopy.addAll(union);

    // First step is to copy all nondominated individuals, i.e., those
    // which have a fitness lower than one, from archive and population
    // to the archive of the next generation
    List tmpPopulation = new ArrayList(populationCopy.size());
    Iterator it = populationCopy.iterator();
    while (it.hasNext()) {
      T individual = it.next();
      if (individual.getDistance() < 1.0) {
        tmpPopulation.add(individual);
        it.remove();
      }
    }

    // If the nondominated front fits exactly into the archive, the environmental
    // selection step is completed
    if (tmpPopulation.size() == Properties.POPULATION) {
      return tmpPopulation;
    }
    // If archive is too small, the best dominated individuals in the previous
    // archive and population are copied to the new archive
    else if (tmpPopulation.size() < Properties.POPULATION) {
      Collections.sort(populationCopy, new StrengthFitnessComparator());
      int remain = (union.size() < Properties.POPULATION ? union.size() : Properties.POPULATION) - tmpPopulation.size();
      for (int i = 0; i < remain; i++) {
        tmpPopulation.add(populationCopy.get(i));
      }

      return tmpPopulation;
    }

    // when the size of the current nondominated (multi)set exceeds the archive size,
    // an archive truncation procedure is invoked which iteratively removes individuals
    // from the new front until if fits exactly into the archive. the individual which
    // has the minimum distance to another individual is chosen at each stage; if there
    // are several individuals with minimum distance the tie is broken by considering the
    // second smallest distances and so forth.

    double[][] distance = this.euclideanDistanceMatrix(tmpPopulation);

    List>> distanceList = new LinkedList>>();
    for (int i = 0; i < tmpPopulation.size(); i++) {
      List> distanceNodeList = new LinkedList>();

      for (int j = 0; j < tmpPopulation.size(); j++) {
        if (i != j) {
          distanceNodeList.add(Pair.of(j, distance[i][j]));
        }
      }

      // sort by distance so that later we can just get the first element, i.e.,
      // the one with the smallest distance
      Collections.sort(distanceNodeList, new Comparator>() {
        @Override
        public int compare(Pair pair1, Pair pair2) {
          if (pair1.getRight() < pair2.getRight()) {
            return -1;
          } else if (pair1.getRight() > pair2.getRight()) {
            return 1;
          } else {
            return 0;
          }
        }
      });

      distanceList.add(distanceNodeList);
    }

    while (tmpPopulation.size() > Properties.POPULATION) {
      double minDistance = Double.POSITIVE_INFINITY;
      int minimumIndex = -1;

      for (int i = 0; i < distanceList.size(); i++) {
        List> distances = distanceList.get(i);
        Pair point = distances.get(0);

        // as this list is sorted, we just need to get the first element of it.
        if (point.getRight() < minDistance) {
          minDistance = point.getRight();
          minimumIndex = i;
        } else if (point.getRight() == minDistance) {
          // as there is a tie, the th smallest distances as to to be searched for

          // find the k-th smallest distance that is not equal to the one just
          // selected. i.e., go through all distances and skip the ones that
          // are equal.
          for (int k = 0; k < distances.size(); k++) {
            double kdist1 = distances.get(k).getRight();
            double kdist2 = distanceList.get(minimumIndex).get(k).getRight();

            if (kdist1 == kdist2) {
              continue;
            } else if (kdist1 < kdist2) {
              minimumIndex = i;
            }

            break;
          }
        }
      }

      assert minimumIndex != -1;

      // remove the solution with the smallest distance
      tmpPopulation.remove(minimumIndex);
      distanceList.remove(minimumIndex);

      // remove from the neighbours' list of neighbours, the one we just removed
      for (List> distances : distanceList) {
        ListIterator> iterator = distances.listIterator();
        while (iterator.hasNext()) {
          if (iterator.next().getLeft() == minimumIndex) {
            iterator.remove();
            // TODO can we break the loop? is there any chance that 'distances'
            // has repeated elements?!
          }
        }
      }
    }

    return tmpPopulation;
  }

  /**
   * 
   * @param solutions
   */
  protected void computeStrength(List solution) {
    // count the number of individuals each solution dominates
    int[] strength = new int[solution.size()];
    for (int i = 0; i < solution.size() - 1; i++) {
      for (int j = i + 1; j < solution.size(); j++) {
        int comparison = this.comparator.compare(solution.get(i), solution.get(j));
        if (comparison < 0) {
          strength[i]++;
        } else if (comparison > 0) {
          strength[j]++;
        }
      }
    }

    // the raw fitness is the sum of the dominance counts (strength)
    // of all dominated solutions
    double[] rawFitness = new double[solution.size()];
    for (int i = 0; i < solution.size() - 1; i++) {
      for (int j = i + 1; j < solution.size(); j++) {
        int comparison = this.comparator.compare(solution.get(i), solution.get(j));
        if (comparison > 0) {
          rawFitness[i] += strength[j];
        } else if (comparison < 0) {
          rawFitness[j] += strength[i];
        }
      }
    }

    // Add the distance to the k-th individual. In the reference paper of SPEA2,
    // k = sqrt(population.size()), but a value of k = 1 is recommended. See
    // http://www.tik.ee.ethz.ch/pisa/selectors/spea2/spea2_documentation.txt

    double[][] distance = this.euclideanDistanceMatrix(solution);
    int k = 1;
    for (int i = 0; i < distance.length; i++) {
      Arrays.sort(distance[i]);
      double kDistance = 1.0 / (distance[i][k] + 2.0);
      // TODO for now let's use 'distance' field, however the right
      // name should be 'strength' or 'fitness-strength'
      solution.get(i).setDistance(rawFitness[i] + kDistance);
    }
  }

  /**
   * Returns a matrix with the euclidean distance between each pair of solutions in the population.
   * 
   * @param solution
   * @return
   */
  protected double[][] euclideanDistanceMatrix(List solution) {
    double[][] distance = new double[solution.size()][solution.size()];

    for (int i = 0; i < solution.size(); i++) {
      distance[i][i] = 0.0;
      for (int j = i + 1; j < solution.size(); j++) {
        distance[i][j] = this.distanceBetweenObjectives(solution.get(i), solution.get(j));
        distance[j][i] = distance[i][j];
      }
    }

    return distance;
  }

  /**
   * Returns the euclidean distance between a pair of solutions in the objective space.
   * 
   * @param t1
   * @param t2
   * @return
   */
  protected double distanceBetweenObjectives(T t1, T t2) {
    double distance = 0.0;

    // perform euclidean distance
    for (FitnessFunction ff : t1.getFitnessValues().keySet()) {
      double diff = t1.getFitness(ff) - t2.getFitness(ff);
      distance += Math.pow(diff, 2.0);
    }

    return Math.sqrt(distance);
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy