org.opt4j.optimizers.ea.Spea2 Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of opt4j-optimizers Show documentation
Show all versions of opt4j-optimizers Show documentation
A set of optimizers for Opt4J
The newest version!
/*******************************************************************************
* Copyright (c) 2014 Opt4J
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*******************************************************************************/
package org.opt4j.optimizers.ea;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.opt4j.core.Individual;
import org.opt4j.core.Objectives;
import org.opt4j.core.common.random.Rand;
import org.opt4j.core.start.Constant;
import com.google.inject.Inject;
/**
*
* The {@link Spea2}-Selector is a Java implementation of the SPEA2-MOEA, see "SPEA2: Improving the Strength Pareto Evolutionary
* Algorithm For Multiobjective Optimization, Eckart Zitzler, Marco Laumanns,
* and Lothar Thiele, In Evolutionary Methods for Design, Optimisation, and
* Control, pages 19–26, 2002.".
*
*
* The {@link Spea2}-Selector will not work properly if the {@link Objectives}
* are not fixed, i.e., the {@link Objectives} of an {@link Individual} change
* during the optimization process. This caused by the internal caching of
* distances, etc. In this case, it is recommended to use the {@link Nsga2}.
*
*
* @author lukasiewycz
*
*/
public class Spea2 implements Selector {
protected final Rand random;
protected final int tournament;
protected final Map map = new LinkedHashMap();
protected final Set individualSets = new LinkedHashSet();
protected final LinkedList freeIDs = new LinkedList();
protected double[][] distance;
protected boolean fitnessDirty = true;
/**
* A wrapper for multiple equal (based on their objectives)
* {@link Individual}s.
*
* @author lukasiewycz
*
*/
private static class Spea2IndividualSet extends LinkedHashSet implements Comparable {
private static final long serialVersionUID = 1L;
protected final int id;
protected int fitness;
protected int strength;
protected final Objectives objectives;
protected double nextDistance = 0;
Spea2IndividualSet(Individual individual, int id) {
this.id = id;
this.add(individual);
this.objectives = individual.getObjectives();
this.fitness = 0;
this.strength = 0;
}
public Objectives getObjectives() {
return objectives;
}
public int getFitness() {
return fitness;
}
public void setFitness(int fitness) {
this.fitness = fitness;
}
public int getStrength() {
return strength;
}
public void setStrength(int strength) {
this.strength = strength;
}
public Individual first() {
assert (size() != 0);
return iterator().next();
}
public int getId() {
return id;
}
public double getNextDistance() {
return nextDistance;
}
public void setNextDistance(double nextDistance) {
this.nextDistance = nextDistance;
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
Spea2IndividualSet other = (Spea2IndividualSet) obj;
return (id == other.id);
}
public boolean dominates(Spea2IndividualSet individualSet) {
return this.getObjectives().dominates(individualSet.getObjectives());
}
@Override
public int compareTo(Spea2IndividualSet o) {
return o.fitness - this.fitness;
}
}
/**
* Constructs a {@link Spea2}-{@link Selector}.
*
* @param tournament
* the number of individuals that fight against each other to
* become a parent
* @param random
* the random number generator
*/
@Inject
public Spea2(@Constant(value = "tournament", namespace = Spea2.class) int tournament, Rand random) {
this.tournament = tournament;
this.random = random;
}
/*
* (non-Javadoc)
*
* @see org.opt4j.optimizer.ea.Selector#init(int)
*/
@Override
public void init(int maxsize) {
for (int i = 0; i < maxsize; i++) {
freeIDs.add(i);
}
distance = new double[maxsize][maxsize];
}
/*
* (non-Javadoc)
*
* @see org.opt4j.optimizer.ea.Selector#getParents(int,
* java.util.Collection)
*/
@Override
public Collection getParents(int mu, Collection population) {
update(population);
List parents = new ArrayList();
List candidates = new ArrayList(population);
int size = candidates.size();
for (int i = 0; i < mu; i++) {
Individual winner = candidates.get(random.nextInt(size));
for (int j = 0; j < tournament; j++) {
Individual opponent = candidates.get(random.nextInt(size));
Spea2IndividualSet wWinner = map.get(winner);
Spea2IndividualSet wOpponent = map.get(opponent);
double oFitness = wWinner.getFitness();
double wFitness = wOpponent.getFitness();
if (oFitness > wFitness || (winner == opponent)) {
winner = opponent;
} else if (oFitness == wFitness) {
double oDist = getMinDistance(wOpponent);
double wDist = getMinDistance(wWinner);
if (oDist > wDist) {
winner = opponent;
}
}
}
parents.add(winner);
}
assert (parents.size() == mu);
return parents;
}
/*
* (non-Javadoc)
*
* @see org.opt4j.optimizer.ea.Selector#getLames(int, java.util.Collection)
*/
@Override
public Collection getLames(int lambda, Collection population) {
update(population);
assert (lambda <= population.size());
Set lames = new LinkedHashSet();
if (lambda > 0) {
List dominated = getDominated();
if (countIndividuals(dominated) >= lambda) {
Collections.sort(dominated);
List lameCandidates = new ArrayList();
int i = 0;
while (lameCandidates.size() < lambda) {
lameCandidates.addAll(dominated.get(i));
i++;
}
lames.addAll(lameCandidates.subList(0, lambda));
} else {
for (Spea2IndividualSet w0 : dominated) {
lames.addAll(w0);
}
for (Individual individual : getLamesFromNonDominated(lambda - lames.size())) {
lames.add(individual);
}
}
}
assert (lames.size() == lambda);
return lames;
}
/**
* Returns a specific number of lames from the non-dominated
* {@link Individual}s.
*
* @param count
* the specified number
* @return a collection of the lame individuals
*/
public Collection getLamesFromNonDominated(int count) {
Set set = new LinkedHashSet();
while (set.size() < count) {
int maxsize = 0;
List candidates = new ArrayList();
for (Spea2IndividualSet individualSet : getNonDominated()) {
if (individualSet.size() > maxsize) {
maxsize = individualSet.size();
candidates.clear();
}
if (individualSet.size() == maxsize) {
candidates.add(individualSet);
}
}
if (candidates.size() <= (count - set.size())) {
for (Spea2IndividualSet individualSet : candidates) {
Individual individual = individualSet.first();
remove(individual);
set.add(individual);
}
} else { // candidates.size() > (count - set.size())
for (Spea2IndividualSet individualSet : getNearest(count - set.size(), candidates)) {
Individual individual = individualSet.first();
remove(individual);
set.add(individual);
}
}
}
assert (set.size() == count);
return set;
}
/**
* Returns n with nearest neighbor based on distances.
*
* @param n
* the number of required IndividualSets
* @param candidates
* the candidate IndividualSets
* @return the n nearest neighbors
*/
protected List getNearest(int n, Collection candidates) {
assert (candidates.size() > n);
List lames = new ArrayList();
Map> orderedLists = new LinkedHashMap>();
for (Spea2IndividualSet w0 : candidates) {
List list = new ArrayList();
for (Spea2IndividualSet w1 : candidates) {
if (w0 != w1) {
w1.setNextDistance(distance(w0, w1));
list.add(w1);
}
}
Collections.sort(list, new Comparator() {
@Override
public int compare(Spea2IndividualSet o1, Spea2IndividualSet o2) {
double v = o1.getNextDistance() - o2.getNextDistance();
if (v < 0) {
return -1;
} else if (v > 0) {
return 1;
} else {
return 0;
}
}
});
orderedLists.put(w0, list);
}
while (lames.size() < n) {
List lcandidates = new ArrayList(orderedLists.keySet());
int size = lcandidates.size();
for (int k = 0; k < size - 1; k++) {
double min = Double.MAX_VALUE;
for (Spea2IndividualSet candidate : lcandidates) {
double value = distance(candidate, orderedLists.get(candidate).get(k));
min = Math.min(min, value);
}
for (Iterator it = lcandidates.iterator(); it.hasNext();) {
Spea2IndividualSet candidate = it.next();
double value = distance(candidate, orderedLists.get(candidate).get(k));
if (value > min) {
it.remove();
}
}
if (lcandidates.size() == 1) {
break;
}
}
Spea2IndividualSet lame = lcandidates.get(0);
lames.add(lame);
orderedLists.remove(lame);
for (List list : orderedLists.values()) {
list.remove(lame);
}
}
assert (lames.size() == n);
return lames;
}
protected double getMinDistance(Spea2IndividualSet w0) {
double min = Double.MAX_VALUE;
for (Spea2IndividualSet w1 : individualSets) {
if (w0 != w1) {
min = Math.min(min, distance(w0, w1));
}
}
return min;
}
/**
* Update with current population.
*
* @param population
* the current population
*/
protected void update(Collection population) {
Set popSet = new LinkedHashSet(population);
if (!popSet.equals(map.keySet())) {
Set adds = new LinkedHashSet(popSet);
adds.removeAll(map.keySet());
Set removes = new LinkedHashSet(map.keySet());
removes.removeAll(popSet);
for (Individual individual : removes) {
remove(individual);
}
for (Individual individual : adds) {
add(individual);
}
}
assert (population.size() == map.size());
if (fitnessDirty) {
calculateFitness();
fitnessDirty = false;
}
}
/**
* Return the distance of two {@code Spea2IndividualSet}s.
*
* @param w0
* first set
* @param w1
* second set
* @return the distance
*/
protected double distance(Spea2IndividualSet w0, Spea2IndividualSet w1) {
return distance[w0.getId()][w1.getId()];
}
/**
* Add a new {@link Individual}.
*
* @param individual
* the individual to add
*/
protected void add(Individual individual) {
int id0 = freeIDs.removeFirst();
Spea2IndividualSet w0 = new Spea2IndividualSet(individual, id0);
Spea2IndividualSet eq = null;
for (Spea2IndividualSet w1 : individualSets) {
int id1 = w1.getId();
double dist = calculateDistance(w0, w1);
if (dist == 0.0) {
eq = w1;
break;
}
distance[id0][id1] = dist;
distance[id1][id0] = dist;
}
if (eq != null) {
freeIDs.add(id0);
w0 = eq;
w0.add(individual);
} else {
distance[id0][id0] = 0.0;
individualSets.add(w0);
}
map.put(individual, w0);
fitnessDirty = true;
}
/**
* Remove an {@link Individual}.
*
* @param individual
* the individual to remove
*/
protected void remove(Individual individual) {
Spea2IndividualSet individualSet = map.remove(individual);
if (individualSet.size() == 1) {
individualSets.remove(individualSet);
freeIDs.add(individualSet.getId());
} else {
individualSet.remove(individual);
}
fitnessDirty = true;
}
/**
* Calculate the distance between two {@code Spea2IndividualSet}s.
*
* @param w0
* the first set
* @param w1
* the second set
* @return the distance
*/
protected double calculateDistance(Spea2IndividualSet w0, Spea2IndividualSet w1) {
return w0.getObjectives().distance(w1.getObjectives());
}
/**
* Calculate the fitness.
*/
protected void calculateFitness() {
for (Spea2IndividualSet individual : individualSets) {
int s = 0;
for (Spea2IndividualSet other : individualSets) {
if (individual != other && individual.dominates(other)) {
s += other.size();
}
}
individual.setStrength(s);
}
for (Spea2IndividualSet individual : individualSets) {
int f = 0;
for (Spea2IndividualSet other : individualSets) {
if (individual != other && other.dominates(individual)) {
f += other.getStrength() * other.size();
}
}
individual.setFitness(f);
}
}
/**
* Returns all dominated {@code Spea2IndividualSet}s (fitness > 0).
*
* @return all dominated IndividualSets
*/
protected List getDominated() {
List dominated = new ArrayList();
for (Spea2IndividualSet w0 : individualSets) {
if (w0.getFitness() > 0.0) {
dominated.add(w0);
}
}
return dominated;
}
/**
* Returns all non-dominated {@code Spea2IndividualSet}s (fitness == 0).
*
* @return all non-dominated individualSets
*/
protected List getNonDominated() {
List dominated = new ArrayList();
for (Spea2IndividualSet w0 : individualSets) {
if (w0.getFitness() == 0.0) {
dominated.add(w0);
}
}
return dominated;
}
/**
* Returns the total number of {@link Individual}s in a collection of
* {@code Spea2IndividualSet}s.
*
* @param collection
* the collection of IndividualSets
* @return the total number of Individuals
*/
private int countIndividuals(Collection collection) {
int c = 0;
for (Spea2IndividualSet w : collection) {
c += w.size();
}
return c;
}
}