maths.ts
Version:
Math utilities library for TypeScript, JavaScript and Node.js
221 lines (220 loc) • 8.42 kB
JavaScript
"use strict";
/**
* @author Hector J. Vasquez <ipi.vasquez@gmail.com>
*
* @licence
* 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.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const integers_1 = require("../../discrete/integers");
const world_1 = require("./world");
const debug = require("debug");
const std_ts_1 = require("std.ts");
// Default values
const af = 0.1; // Ant factor
const er = 0.9; // Pheromone evaporation rate
const BETA = 1.9; // Trail preference
const ALPHA = 1.7; // Greedy preference
/**
* Contains collective information retrieved by the ants of this colony.
* Furthermore, it determines when to move ants and how they must behave.
*/
class AntColony {
/**
* Creates a new colony of ants (antFactor*towns.length ants).
* @param towns An array holding the coordinates of each town.
*/
constructor(towns) {
this.debug = debug('Colony');
this.debug('Creating World of Towns');
this.world = new world_1.World(towns);
}
/**
* Performs the Ant Colony Optimization by putting each ant to find a
* solution.
* @param iterations The number of times to reach a new solution per ant.
* @param antFactor This value multiplied by the number of towns
* delivers the number of ants to be used.
* @param evaporationRate The rules of how fast old pheromones evaporate.
* @param alpha The pheromone preference.
* @param beta The greedy preference.
*/
optimize(iterations = 5, antFactor = af, evaporationRate = er, alpha = ALPHA, beta = BETA) {
return __awaiter(this, void 0, void 0, function* () {
this.debug('Generating ants');
// Generating ants
const ants = Math.ceil(antFactor * this.world.towns.length);
this.ants = [];
for (let i = 0; i < ants; i++) {
this.ants.push(new Ant(i, this.world, alpha, beta));
}
this.debug('Putting initial pheromones');
this.world.reset();
// Performing optimization
let best = {
path: undefined,
distance: Infinity
};
const promises = [];
for (let k = 0; k < iterations; k++) {
for (let j = 0; j < this.ants.length; j++) {
promises[j] = this.ants[j].tour();
}
yield Promise.all(promises).then(solutions => {
// Finds best solution
solutions.forEach(s => {
if (best.distance > s.distance) {
best = s;
}
// Experimental improvement: The idea is that if the
// final distance after this solutions is good, then
// reinforcement of each edge on this path would improve
// even more the solution
/// Reference: ACO Algorithm with Additional Reinforcement
/// https://link.springer.com/chapter/10.1007/3-540-45724-0_31
const p = world_1.K / s.distance;
for (let i = 0; i < s.path.length; i++) {
const next = (i + 1) % s.path.length;
this.world.towns[s.path[i]].edges[s.path[next]].pheromones += p;
}
});
});
this.world.evaporate(evaporationRate);
}
return best;
});
}
}
exports.AntColony = AntColony;
class Ant {
/**
* Creates a new ant.
* @param id The id for this ant.
* @param world The world where this ant is.
* @param alpha Preference given to ...
* @param beta Preference given to ...
*/
constructor(id, world, alpha, beta) {
this.id = id;
this.world = world;
this.alpha = alpha;
this.beta = beta;
this.debug = debug('Ant ' + id);
this.distanceTraveled = 0;
// Update list of towns left
this.townsVisited = [];
// Create origin
this.path = [integers_1.randInt(0, this.towns.length)];
this.debug('Starting town selected: ' + this.town);
this.townsVisited[this.town] = true;
}
/**
* Gets the towns on this ant's world.
* @returns The towns on this ant's world.
*/
get towns() {
return this.world.towns;
}
/**
* Gets the current town where the ant is.
* @returns The town where this ant is.
*/
get town() {
return this.path[this.path.length - 1];
}
/**
* Returns the current path.
*/
get curSolution() {
// Distance from end to start
const d = this.towns[this.town].edges[this.path[0]].distance;
return {
path: this.path.slice(),
// Travel distance + distance from end to start
distance: this.distanceTraveled + d
};
}
/**
* Finds a probabilistic route executing next town.
* @returns {Promise<Solution>}
*/
tour() {
return __awaiter(this, void 0, void 0, function* () {
while (this.path.length < this.towns.length) {
this.tourNext();
}
this.debug('Route found. Distance: ' + this.curSolution.distance);
return this.curSolution;
});
}
/**
* Visits next town.
*/
tourNext() {
const nearest = this.findNearest(15);
// Create roulette
const pp = [];
let sum = 0;
nearest.forEach(t => {
const neighbor = this.towns[t];
const p = Math.pow(neighbor.edges[this.town].pheromones, this.alpha) +
Math.pow((world_1.K / neighbor.edges[this.town].distance), this.beta);
sum += p;
pp.push(p);
});
for (let i = 0; i < pp.length; i++) {
pp[i] /= sum;
if (i) {
pp[i] += pp[i - 1];
}
}
// Last must be 1
pp[pp.length - 1] = 1;
// Randomly biased select the next town
let nextTown = std_ts_1.upperBound(pp, 0, pp.length, Math.random());
nextTown = nearest[nextTown];
// this.debug('Next town: ' + nextTown);
const d = this.towns[this.town].edges[nextTown].distance;
this.distanceTraveled += d;
this.towns[this.town].edges[nextTown].pheromones += world_1.K / d;
this.path.push(nextTown);
this.townsVisited[this.town] = true;
}
/**
* Finds the nearest available towns from this ant location.
* @param limit The max number of neighbors wanted.
* @returns The nearest towns from this ant location.
*/
findNearest(limit = Infinity) {
// The current town where the ant is
const neighbors = [];
for (let i = 0; i < this.towns[this.town].neighbors.length; i++) {
if (neighbors.length >= limit) {
break;
}
if (!this.townsVisited[this.towns[this.town].neighbors[i]]) {
neighbors.push(this.towns[this.town].neighbors[i]);
}
}
return neighbors;
}
}
exports.Ant = Ant;