Tutorial 3: Travelling Salesman Problem and Memetic Algorithms

In this tutorial, we shall use Wallace to implement a generic algorithm to solve the travelling salesman problem, in which we wish to find the shortest possible route through a given set of cities, which visits all cities exactly once and return to the city at which the tour was started. The TSP is a prime example of an NP-hard, or more specifically, NP-complete, problem that can be effectively tackled using techniques such as genetic algorithms and ant colony optimisation.

This tutorial assumes:

  • A basic knowledge of Julia.
  • You know how to create and run a basic genetic algorithm within Wallace.

By the end of this tutorial, you will be able to:

  • Implement memetic algorithms via local search operators, incorporated using the linear breeder.
  • Extend Wallace with a simple evaluator, tailored to the travelling salesman problem.
  • Use Wallace to implement genetic algorithms capable of solving permutation-based problems, such as the travelling salesman problem.

The Problem

Could do with a short description of the problem being solved in this tutorial, perhaps along with a diagram of the Berlin-52 map, and links to the .tsp file.

Basic Setup

For this problem, we shall be using a standard evolutionary algorithm, with the components listed below:

Component Setting
Replacement Scheme Generational (without elitism)
Population Simple (single deme)
Representation Permutation
Breeder Linear Breeder

Solution Representation

As in the previous tutorial, we will once again be using the simple species to describe our simple population. In this case, we will be using a permutation representation to represent our potential solutions; each tour is represented as an itinerary, where the cities are listed in the order in which they are visited, except for the return trip to the starting city, which is left out as that part of the journey is implicit.

Instances of the permutation representation are specified by providing an alphabet of values which they should permute; this alphabet may contain any type of item, from strings, to integers, to arbitary objects. One may provide a alphabet to the specification either by explicitly stating it within a list, by providing a numeric range, or by providing an external alphabet file.

If one were to take the explicit approach to representing the alphabet for the given problem, then the specification would look something like that given below:

_my_species<species/simple>:
  representation<representation/permutation>:
    alphabet: [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,
      26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,
      51,52]

Clearly this approach is rather tedious and error-prone for our purposes, especially if we were to use our algorithm to solve other instances of the TSP.

Alternatively, we could store our alphabet in an external line-delimited file, and instead provide the alphabet property with the path to that file.

representation<representation/permutation>:
  alphabet: my_tsp_cities.txt

However, exhaustively listing the indices of each of the cities in our problem, whether inline or through an external alphabet file, is probably still a bit too monotonous for our liking.

Fortunately, we can list the indices for each our cities in far more succinctly through the use of numeric ranges, which are specified in exactly the same way as they are within the Julia language.

representation<representation/permutation>:
  alphabet: 1:52

Setting up the Linear Breeder

The linear breeder is the second simplest breeder provided by Wallace; it relaxes the constraints imposed on the type and number of genetic operators imposed by the simple breeder, allowing the user to provide an arbitrary linear chain of operators instead. Offspring are produced by being subjecting batches of proto-offspring to each of these operators in sequence, until the desired number the required number have been produced as directed.

To specify a linear breeder, one needs only to provide its definition with an ordered list of operators, and if necessary, the stage of individual development upon which they operate, as demonstrated below:

_my_breeder<breeder/linear>:
  operators:
    - type:   selection/tournament
      size:   4
      stage:  genome
    - type:   crossover/pmx
      stage:  genome
    - type:   mutation/2_opt
      stage:  genome

However, since we're using a simple species, which has only a single stage of development, there is no need for us to provide the stage property for each operator specification. In the event we omitted the stage property and our species had more than a single stage of development, then the stage would default to the canonical genotype.

_my_breeder<breeder/linear>:
  operators:
    - type: selection/tournament
      size: 4
    - type: crossover/pmx
    - type: mutation/2_opt

Writing a Custom Evaluator

For our given problem we shall need to write an evaluator capable of (quickly) calculating the round-trip distance of a given tour. The quickest way to do is this to pre-calculate a distance (or cost) matrix, encoding the cost of travelling from one city to another, rather than performing redundant calculations at run-time.

However, this introduces the problem of pre-computing and storing this matrix at the start of the computation, an ability that the simple evaluator is uncapable of. To add this feature into our algorithm we'll have to write our own evaluator from scratch.

In the interests of genericity, we'll write a new evaluator tailored specifically to the Geometric Travelling Salesman Problem, capable of accepting a .tsp file containing a list of city co-ordinates.

To add our own evaluator into Wallace, we'll need to write a type manifest file and register it with Wallace, and in this case, we'll also need to write a new Julia type to realise our evaluator.

Julia Type

type MyTSPEvaluator <: SimpleEvaluator
  cities::Int
  threads::Int
  distance::Array{Int, 2}
end

function evaluate!(e::MyTSPEvaluator, s::State, c::Individual)
  tour = get(c.genome)
  length = zero(Float)
  for i in 1:e.cities-1
    length += e.distance[tour[i], tour[i+1]]
  end
  length + e.distance[tour[end], tour[1]]
end

Wallace Type Manifest

type: alfred#evaluator/tsp
author: Alfred Russel Wallace

description: |
  Evaluates the fitness of a TSP tour for a pre-determined set of cities.

properties:
  threads:
    type:         Integer
    description:  > The number of threads that the evaluation process should be
      split across.

  file:
    type:         String
    description:  > The path to the file containing the co-ordinates of each of
      the cities for the TSP instance being solved.

composer: |
  ...

Running the Algorithm

After having followed all the preceding steps, you should have an algorithm that roughly looks similar to the one given below:

type: algorithm/evolutionary_algorithm

evaluator<evaluator/tsp>:
  cities: berlin52.tsp

replacement<replacement/generational>: {}

termination:
  evaluations<criterion/evaluations>: { limit: 100000 }

_my_species<species/simple>:
  representation<representation/permutation>:
    alphabet: 1:52 

_my_breeder<breeder/linear>:
  operators:
    - type: selection/tournament
      size: 4
    - type: crossover/pmx
    - type: mutation/2_opt

population<population/simple>:
  size:     100
  breeder:  $(_my_breeder)
  species:  $(_my_species)

Visualising the Results