/*
* Artificial Intelligence for Humans
* Volume 2: Nature Inspired Algorithms
* Java Version
* http://www.aifh.org
* http://www.jeffheaton.com
*
* Code repository:
* https://github.com/jeffheaton/aifh
*
* Copyright 2014 by Jeff Heaton
*
* 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.
*
* For more information on Heaton Research copyrights, licenses
* and trademarks visit:
* http://www.heatonresearch.com/copyright
*/
package com.heatonresearch.aifh.aco;
import com.heatonresearch.aifh.randomize.GenerateRandom;
import com.heatonresearch.aifh.randomize.MersenneTwisterGenerateRandom;
import java.util.ArrayList;
import java.util.List;
/**
* The ant colony optimization (ACO) algorithm finds an optimal path through a graph. It works by establishing
* pheromone trails between the graph nodes. The pheromone trails increase in strength as ants travel over the
* edges of the graph. The pheromone trails decrease over time. The discrete version of ACO arranges a path
* to visit the nodes of a graph, that minimizes cost.
* <p/>
* References:
* <p/>
* http://en.wikipedia.org/wiki/Ant_colony_optimization_algorithms
* <p/>
* M. Dorigo, Optimization, Learning and Natural Algorithms, PhD thesis, Politecnico di Milano, Italy, 1992.
*/
public class DiscreteACO {
/**
* The pheromone trails between graph segments.
*/
private final double[][] pheromone;
/**
* A graph of costs.
*/
private final CostGraph graph;
/**
* The ants.
*/
private final List<DiscreteAnt> ants = new ArrayList<DiscreteAnt>();
/**
* The initial value of the pheromone trails.
*/
public static double INITIAL_PHEROMONE = 1.0;
/**
* Constant that defines the attractiveness of the pheromone trail.
*/
private double alpha = 1;
/**
* Constant that defines the attractiveness of better state transitions (from one node to another).
*/
private double beta = 5;
/**
* Constant that defines how quickly the pheromone path evaporates.
*/
private double evaporation = 0.5;
/**
* The amount of pheromone that the nodes of a path share for a trip.
*/
private double q = 500;
/**
* The base probability.
*/
private double pr = 0.01;
/**
* A random number generator.
*/
private GenerateRandom random = new MersenneTwisterGenerateRandom();
/**
* The current best path.
*/
private final int[] bestPath;
/**
* The cost of the best path. We are trying to minimize this.
*/
private double bestCost;
/**
* The constructor.
*
* @param theGraph The graph that we are seeking a minimal path through.
* @param theAntCount The number of ants to use.
*/
public DiscreteACO(CostGraph theGraph, int theAntCount) {
int len = theGraph.graphSize();
this.graph = theGraph;
this.pheromone = new double[len][len];
this.bestPath = new int[len];
this.bestCost = Double.POSITIVE_INFINITY;
for (int i = 0; i < len; i++) {
for (int j = 0; j < len; j++) {
this.pheromone[i][j] = INITIAL_PHEROMONE;
}
}
for (int i = 0; i < theAntCount; i++) {
ants.add(new DiscreteAnt(len));
}
}
/**
* Calculate the probability of a given ant moving to any of the next nodes.
*
* @param currentIndex The index into the path.
* @param ant The ant.
* @return The probability of moving to the next node.
*/
private double[] calculateProbability(int currentIndex, DiscreteAnt ant) {
double[] result = new double[this.graph.graphSize()];
int i = ant.getPath()[currentIndex - 1];
double d = 0.0;
for (int l = 0; l < this.graph.graphSize(); l++)
if (!ant.wasVisited(l))
d += Math.pow(this.pheromone[i][l], alpha)
* Math.pow(1.0 / this.graph.cost(i, l), beta);
for (int j = 0; j < this.graph.graphSize(); j++) {
if (ant.wasVisited(j)) {
result[j] = 0.0;
} else {
double n = Math.pow(this.pheromone[i][j], alpha)
* Math.pow(1.0 / this.graph.cost(i, j), beta);
result[j] = n / d;
}
}
return result;
}
/**
* Choose the next node for an ant to visit. This is based on probability.
*
* @param currentIndex The step we are at in the path.
* @param ant The ant being evaluated.
* @return The node we will move into.
*/
private int pickNextNode(int currentIndex, DiscreteAnt ant) {
if (currentIndex == 0 || this.random.nextDouble() < pr) {
int index;
do {
index = this.random.nextInt(0, this.graph.graphSize());
} while (ant.wasVisited(index));
return index;
}
double[] prob = calculateProbability(currentIndex, ant);
double r = this.random.nextDouble();
double sum = 0;
for (int i = 0; i < this.graph.graphSize(); i++) {
sum += prob[i];
if (sum >= r)
return i;
}
// should not happen!
return -1;
}
/**
* Update the pheromone levels both for ants traveling and evaporation.
*/
private void updatePheromone() {
// Cause evaporation.
for (int i = 0; i < this.pheromone.length; i++)
for (int j = 0; j < this.pheromone[i].length; j++)
this.pheromone[i][j] *= evaporation;
// Adjust for ants.
for (DiscreteAnt a : ants) {
double d = q / a.calculateCost(this.graph.graphSize(), this.graph);
for (int i = 0; i < this.graph.graphSize() - 1; i++) {
this.pheromone[a.getPath()[i]][a.getPath()[i + 1]] += d;
}
this.pheromone[a.getPath()[this.graph.graphSize() - 1]][a.getPath()[0]] += d;
}
}
/**
* Move the ants forward on their path.
*/
private void march() {
for (int currentIndex = 0; currentIndex < this.graph.graphSize(); currentIndex++) {
for (DiscreteAnt a : this.ants) {
int next = pickNextNode(currentIndex, a);
a.visit(currentIndex, next);
}
}
}
/**
* Reset the ants.
*/
private void setupAnts() {
for (DiscreteAnt a : this.ants) {
a.clear();
}
}
/**
* Update the best path.
*/
private void updateBest() {
int[] bestPathFound = null;
for (DiscreteAnt a : this.ants) {
double cost = a.calculateCost(this.graph.graphSize(), this.graph);
if (cost < this.bestCost) {
bestPathFound = a.getPath();
this.bestCost = cost;
}
}
if (bestPathFound != null) {
System.arraycopy(bestPathFound, 0, this.bestPath, 0, this.bestPath.length);
}
}
/**
* Perform one iteration.
*/
public void iteration() {
setupAnts();
march();
updatePheromone();
updateBest();
}
/**
* @return The random number generator.
*/
public GenerateRandom getRandom() {
return random;
}
public void setRandom(final GenerateRandom random) {
this.random = random;
}
/**
* @return The best tour/path.
*/
public int[] getBestTour() {
return bestPath;
}
public double getBestCost() {
return this.bestCost;
}
/**
* @return The pheromone levels.
*/
public double[][] getPheromone() {
return pheromone;
}
/**
* @return The cost graph.
*/
public CostGraph getGraph() {
return graph;
}
/**
* @return The ants.
*/
public List<DiscreteAnt> getAnts() {
return ants;
}
/**
* @return Constant that defines the attractiveness of the pheromone trail.
*/
public double getAlpha() {
return alpha;
}
/**
* Set the constant that defines the attractiveness of the pheromone trail.
*
* @param alpha The constant that defines the attractiveness of the pheromone trail.
*/
public void setAlpha(final double alpha) {
this.alpha = alpha;
}
/**
* @return Constant that defines the attractiveness of better state transitions (from one node to another).
*/
public double getBeta() {
return beta;
}
/**
* Constant that defines the attractiveness of better state transitions (from one node to another).
*
* @param beta The constant that defines the attractiveness of better state transitions (from one node to another).
*/
public void setBeta(final double beta) {
this.beta = beta;
}
/**
* @return The pheromone evaporation level.
*/
public double getEvaporation() {
return evaporation;
}
/**
* Set the pheromone evaporation level.
*
* @param evaporation The pheromone evaporation level.
*/
public void setEvaporation(final double evaporation) {
this.evaporation = evaporation;
}
/**
* @return The amount of pheromone that the nodes of a path share for a trip.
*/
public double getQ() {
return q;
}
/**
* Set the amount of pheromone that the nodes of a path share for a trip.
*
* @param q The amount of pheromone that the nodes of a path share for a trip.
*/
public void setQ(final double q) {
this.q = q;
}
/**
* @return The base probability.
*/
public double getPr() {
return pr;
}
/**
* Set the base probability.
*
* @param pr The base probability.
*/
public void setPr(final double pr) {
this.pr = pr;
}
/**
* @return The best path.
*/
public int[] getBestPath() {
return bestPath;
}
}