CSA applied to the travelling salesman problem (TSP)

This example was adapted from https://github.com/perrygeo/simanneal/blob/master/examples/salesman.py, which applies (single) SA to the TSP.


In [1]:
from __future__ import print_function

import math
import random

from pycsa import CoupledAnnealer

try:
    xrange
except NameError:
    xrange = range

Let's create a set of cities to use for TSP.


In [2]:
cities = {
    'New York City': (40.72, 74.00),
    'Los Angeles': (34.05, 118.25),
    'Chicago': (41.88, 87.63),
    'Houston': (29.77, 95.38),
    'Phoenix': (33.45, 112.07),
    'Philadelphia': (39.95, 75.17),
    'San Antonio': (29.53, 98.47),
    'Dallas': (32.78, 96.80),
    'San Diego': (32.78, 117.15),
    'San Jose': (37.30, 121.87),
    'Detroit': (42.33, 83.05),
    'San Francisco': (37.78, 122.42),
    'Jacksonville': (30.32, 81.70),
    'Indianapolis': (39.78, 86.15),
    'Austin': (30.27, 97.77),
    'Columbus': (39.98, 82.98),
    'Fort Worth': (32.75, 97.33),
    'Charlotte': (35.23, 80.85),
    'Memphis': (35.12, 89.97),
    'Baltimore': (39.28, 76.62)
}

Now's lets define the function to calculate distances between cities and create a distance matrix.


In [3]:
def distance(a, b):
    """
    Helper function to calculate the distance between two 
    latitude-longitude coordinates.
    """
    R = 3963  # radius of Earth (miles)
    lat1, lon1 = math.radians(a[0]), math.radians(a[1])
    lat2, lon2 = math.radians(b[0]), math.radians(b[1])
    return math.acos(math.sin(lat1) * math.sin(lat2) +
                     math.cos(lat1) * math.cos(lat2) * 
                     math.cos(lon1 - lon2)) * R

# Create the distance matrix between the cities.
distance_matrix = {}
for ka, va in cities.items():
    distance_matrix[ka] = {}
    for kb, vb in cities.items():
        if kb == ka:
            distance_matrix[ka][kb] = 0.0
        else:
            distance_matrix[ka][kb] = distance(va, vb)

Next we have to define the target_function, i.e. the cost function to be minimized, and the probe_function, which will randomly update the current state at each annealing process.


In [4]:
def probe(positions, tgen):
    """
    Swap two cities in the route.
    
    Note that `tgen` (the generation temperature) is ignored here.
    In general, you can use `tgen` to adjust the variance of
    the probing jumps as the algorithm progress.
    """
    a = random.randint(0, len(positions) - 1)
    b = random.randint(0, len(positions) - 1)
    positions[a], positions[b] = positions[b], positions[a]
    return positions


def target(positions):
    """
    Calculates the length of the route.
    """
    e = 0
    for i in xrange(len(positions)):
        e += distance_matrix[positions[i-1]][positions[i]]
    return e

Okay let's give it a run!


In [9]:
n_annealers = 10  # the number of coupled annealers

init_state = list(cities.keys())
random.shuffle(init_state)

# Initialize the CSA process.
annealer = CoupledAnnealer(
    target, 
    probe, 
    initial_state=[init_state] * n_annealers,
    steps=100,  # You probably want to set this a lot higher, like 10,000
    processes=1,   # Only use more than 1 process if the target function is costly to compute
    n_annealers=n_annealers,
    tacc_initial=1000.0,
    verbose=1)

# Beging the annealing.
annealer.anneal()


Step      5, Energy 20,091.0525
Updated acceptance temp 950.000000
Updated generation temp 0.010000

Step     10, Energy 16,484.3502
Updated acceptance temp 902.500000
Updated generation temp 0.010000

Step     15, Energy 16,484.3502
Updated acceptance temp 857.375000
Updated generation temp 0.010000

Step     20, Energy 15,271.2471
Updated acceptance temp 814.506250
Updated generation temp 0.010000

Step     25, Energy 13,225.6880
Updated acceptance temp 773.780937
Updated generation temp 0.010000

Step     30, Energy 13,225.6880
Updated acceptance temp 735.091891
Updated generation temp 0.009999

Step     35, Energy 13,148.0035
Updated acceptance temp 771.846485
Updated generation temp 0.009999

Step     40, Energy 13,148.0035
Updated acceptance temp 733.254161
Updated generation temp 0.009999

Step     45, Energy 12,293.5351
Updated acceptance temp 696.591453
Updated generation temp 0.009999

Step     50, Energy 11,544.4658
Updated acceptance temp 661.761880
Updated generation temp 0.009999

Step     55, Energy 11,544.4658
Updated acceptance temp 694.849974
Updated generation temp 0.009999

Step     60, Energy 11,544.4658
Updated acceptance temp 729.592473
Updated generation temp 0.009999

Step     65, Energy 11,318.2325
Updated acceptance temp 766.072097
Updated generation temp 0.009999

Step     70, Energy 11,244.3575
Updated acceptance temp 804.375701
Updated generation temp 0.009999

Step     75, Energy 10,349.4692
Updated acceptance temp 844.594486
Updated generation temp 0.009999

Step     80, Energy 9,678.7316
Updated acceptance temp 886.824211
Updated generation temp 0.009998

Step     85, Energy 8,378.3478
Updated acceptance temp 842.483000
Updated generation temp 0.009998

Step     90, Energy 8,378.3478
Updated acceptance temp 884.607150
Updated generation temp 0.009998

Step     95, Energy 8,378.3478
Updated acceptance temp 928.837508
Updated generation temp 0.009998

Step    100, Energy 8,378.3478
Updated acceptance temp 975.279383
Updated generation temp 0.009998


In [10]:
# Get the best result from all `n_annealers`.
energy, state = annealer.get_best()

# Slide the list of cities until NYC is first.
while state[0] != 'New York City':
    state = state[1:] + state[:1]

print()
print("%i mile route:" % energy)
for city in state:
    print("\t", city)


8378 mile route:
	 New York City
	 Philadelphia
	 San Antonio
	 Austin
	 Houston
	 Memphis
	 Dallas
	 Fort Worth
	 Phoenix
	 San Diego
	 Los Angeles
	 San Francisco
	 San Jose
	 Chicago
	 Detroit
	 Columbus
	 Indianapolis
	 Jacksonville
	 Charlotte
	 Baltimore