SwarmPy is an experimental library.
It aims at providing a modulable framework to test and experiment on Ant Colony Optimzation (ACO) algorithms on Travelling Salesman Problem
contact : lucas.saban[at]ensae.fr
First let's import the librairies we will use
import os
import numpy as np
import matplotlib.pyplot as plt
os.chdir('..')
from swarmpy_tsp import *
The ant_coder
function can load the TSP test sets of the test_set folder, namely :
- berlin52
- ch130
- Any test set you wish, as long you add them to folder following the same format.
G
is the graph representation of the problem. Its a dictionnary containing a uninitialized pheromone matrix, a heuristics matrix and a cost matrix
G, opt_score = Antcoder('test_set/berlin52')
Ant colony optmization algorithms can be built in the same fashion as a Pipeline object from scikit-learn
. You only need to put the blocks in the right order.
Careful ! The G dictionnary is modified inplace during computation. If you want to try different run with the same datapoints, you shall use
deepcopy
from thecopy
built-in library.
Below, you can see an example of an ACO Pipeline
aco_pipeline = ACO_Pipeline(
[
("Planner", Planner({"alpha": 1.0, "beta": 2.0})),
("Sol", SolutionConstructor()),
("DA", DaemonActions()),
("Updater", BestTourPheromonesUpdater()),
],
iter_max=60
)
An ACO_Pipeline
is composed by objects inhering from the semi abstract class ACO_Step
. Those steps are more or less the main composants of any ACO Algorithms and are independently built so that one can use them in other situations or can easily build new blocks.
Let's dive in !
First of all, there is the Planner
step. In very basic use cases, it is not really important and only serves as the place to define your ants parameters. In more advanced cases, such as iteration-dependent ants parameters, that's where the magic happen.
The Planner
object have one parameter which is a dict
of those parameters :
alpha
: coefficient associated to the pheromone trailsbeta
: coefficient associated to the heuristic informationq
: Corresponds to the level of exploitation in Ant Colony System. When not precised, it is set to 0 which is equivalent to Ant Systemmask
: Corresponds to a mask applied on the graph. It is useful when you want to reduce your problem to a subset of cities. If not precized, a generic all covering mask is set.
If you are not familliar with those notations please refer to appropriate ACO litterature (Dorigo, Stützle, ...)
The RandomizedPlanner
object, which inheritates from the Planner
, introduces random sampling of ants parameters. It have two more parameters, alpha_bounds
and beta_bounds
which are the bounds of the uniform distribution from which each ant parameters is drawn.
To add iteration-dependant ant parameters, I'd recommend to inheritate from
Planner
ant induce mutations in therun
method which takes the iteration number as argument.
An example of instanciation :
To keep a parameter constant with
RandomizedPlanner
, setting low_bound = high_bound does the job
planner_step = RandomizedPlanner(alpha_bounds=[1.0, 1.0], beta_bounds=[1., 6.], ant_params={'q':0.8})
The SolutionConstructor
step is at the core of ACO. It is where ants are actually going through the graph to build feasible solutions. No parameters are required.
For now, ants are running concurrently, using the thread
built-in library of Python. Parallel computation should arrive soon.
It should be remarked that solution are sorted by descending quality.
An example of instanciation :
construction_step = SolutionConstructor()
If the first steps are kind of necessary, this one can totally be exempted from the pipeline. It implements a 2-opt local search on the solutions built during the construction step.
It as one parameter, k
, that is the number of best solutions that shall go through local search.
An example of instanciation :
daemon_step = DaemonActions(k=2)
It's the final step of an ACO algorithm iteration. It evaporates pheromones and reinforces pheromone trails according the solutions found. General parameters are :
rho
: the evaporation rate (can be set to 0 for no evaporation)Q
: The normalization parameter of the pheromone reinforcement update (Q/cost)bounds
: if precised, it implements MMAS policy of bounding pheromone trails
Then there is 3 Updaters that inherits from the abstract base Updater :
ProportionnalPheromonesUpdater
: basic updater, reinforcement in Q/cost for all solutionsBestSoFarPheromonesUpdater
: Reinforces thek
best paths found so farBestTourPheromonesUpdater
: Reinforces thek
best paths found in the current lap
Those steps can totally be combined or made iteration-dependant by inheritance.
An example of instanciation :
ph_updater = BestTourPheromonesUpdater(k=2, bounds=[0.1, 1])
The ACO_Pipeline object return the list of all solutions built during computation
solutions_w_daemon = aco_pipeline.run(G=G)
scores_w_daemon = np.array([el[1] for el in solutions_w_daemon['solutions']]) - opt_score
plt.plot(scores_w_daemon)
plt.xlabel('Iterations')
plt.ylabel('Gap between best solution and optimal solution')
plt.show()
SwarmPy | Score : 7859.448968094198: 100%|██████████| 60/60 [00:13<00:00, 4.41it/s]
For an advanced example of combining Pipelines, please refer to the advanced_swarmpy notebook