Professional Documents
Culture Documents
MINI-PROJECT REPORT ON
“Travelling salesman problem using Genetic Algorithm”
Prepared By
Mr. Kyatanavar Prasanna Roll No:86
Miss. Lokhande Sakshi Roll No:88
Mr. Magar Viraj Roll No:92
(B.E. Computer)
[30/2020]
1
ACKNOWLEDGEMENT
First and foremost, We would like to give thanks to God Almighty for His mercy and beautiful
blessings in our life. Even in the hardest times, He had always been there to guide and comfort us
and to make sure that we never stray too far from His narrow way.
We would like to thank our family for their unconditional love and support. And being there
for us, despite all the troubles and problems that grow in life.
Kyatanavar Prasanna(86)
Lokhande Sakshi(88)
Magar Viraj(92)
2
Contents
ABSTRACT:-
Travelling salesman problem (TSP) is a combinatorial optimization problem. It is NP
hard problem and TSP is the most intensively studied problem in the area of optimization. The
travelling salesman problem (TSP) is the most well-known combinatorial optimization problem.
TSP is used to find a routing of a salesman who starts from a home location, visits a
prescribed set of cities and returns to the original location in such a way that the total
3
distance travelled is minimized and each city is visited exactly once. But with the increase in
the number of cities, the complexity of the problem goes on increasing. Genetic Algorithm is
computer algorithm that search for good solutions to a problem from various possible solutions.
The genetic algorithm is a heuristic method which is used to improve the solution space for the
Travelling Salesman Problem. The genetic algorithm results in nearest optimal solution within a
reasonable time. In this system, we have solved Travelling Salesman Problem using Genetic
algorithm approach.
INTRODUCTION :-
Travelling salesman problems are solved using genetic algorithm which solves the
problem by using the processes observed in natural evolution to solve various
optimizations and search problems.
Genetic algorithm improve the solution space for the Travelling Salesman Problem and
results in nearest optimal solution within a reasonable time. The basic elements of
Genetic Algorithm are chromosomes, fitness function, selection, crossover and mutation.
Geographic coordinates of cities are provided as input to generate a edge-weighted
complete graph where the weights are the distance between the cities in kilometers.
SCOPE :-
OBJECTIVE:-
To find optimal solution of travelling salesman problem within reasonable time.
To improve solution space of travelling salesman problems.
METHODOLOGY:
4
Population
Fitness Function
It's the measure used to compare genomes to each other. For this problem a fitness function (and
the one we use in this implementation) is the sum of the edge's weights through the graph for a
certain path - encoded in the genome.
Selection
Each generation must have N genomes at a time. To achieve evolution we select some at the
genomes to Crossover them and produce Offsprings that will be part of the new population. To
select them there are many techniques, two of which are:
1. Roulette Selection:
> sum_of_fitness = 0
2. Tournament Selection:
Crossover
5
Now that we have our parent genomes from the Selection procedure, now we crossover them to
produce - potentially - fitter offsprings.
There are many options and techniques that the Crossover may be done, but the one that we use
in our implementation is the following:
> Any missing values are then added to the offspring from the second parent in order that they
are found.
Example:
Parent 1: a,[b, c],d, e
Parent 2: b, e, a, c, d
Offspring: e, b, c, a, d
Mutation
After making the offsprings of the next generation, to finalize the creation of the new population
we must mutate the genomes.
To mutate a genome we just select two random alleles and swap them in a genome
Note: the mutation does not occur always. It occurs with a user-defined probability.
Elitism
In Genetic Algorithms sometimes the fittest genomes from the current generation are passed
directly to the next, contesting with the offsprings and crossover to - potentially - fitter solutions.
This step occurs during the calculation of the Fitness function value for each genome. When all
values are computed the genome with the minimum value is appended directly to the next
generation's population.
Algorithm Convergence
6
System Requirements:
Software Components:
Operating System : Windows 10.
Pycharm
Matplotlab
Hardware Components:
Processer : i3
RAM : 1GB
Hard Disk : 5 GB
Monitor
Implementation:
1. tsp_ga.py:
from sys import maxsizefrom sys import maxsize
from time import time
from random import random, randint, sample
from haversine import haversine
class Gene: # City
# keep distances from cities saved in a table to improve execution time.
__distances_table = {}
def __init__(self, name, lat, lng):
self.name = name
self.lat = lat
self.lng = lng
def get_distance_to(self, dest):
origin = (self.lat, self.lng)
dest = (dest.lat, dest.lng)
forward_key = origin + dest
backward_key = dest + origin
if forward_key in Gene.__distances_table:
return Gene.__distances_table[forward_key]
7
if backward_key in Gene.__distances_table:
return Gene.__distances_table[backward_key]
dist = int(haversine(origin, dest))
Gene.__distances_table[forward_key] = dist
return dist
class Individual: # Route: possible solution to TSP
def __init__(self, genes):
assert(len(genes) > 3)
self.genes = genes
self.__reset_params()
def swap(self, gene_1, gene_2):
self.genes[0]
a, b = self.genes.index(gene_1), self.genes.index(gene_2)
self.genes[b], self.genes[a] = self.genes[a], self.genes[b]
self.__reset_params()
def add(self, gene):
self.genes.append(gene)
self.__reset_params()
@property
def fitness(self):
if self.__fitness == 0:
self.__fitness = 1 / self.travel_cost # Normalize travel cost
return self.__fitness
@property
def travel_cost(self): # Get total travelling cost
if self.__travel_cost == 0:
for i in range(len(self.genes)):
origin = self.genes[i]
if i == len(self.genes) - 1:
dest = self.genes[0]
else:
dest = self.genes[i+1]
self.__travel_cost += origin.get_distance_to(dest)
return self.__travel_cost
def __reset_params(self):
8
self.__travel_cost = 0
self.__fitness = 0
class Population: # Population of individuals
def __init__(self, individuals):
self.individuals = individuals
@staticmethod
def gen_individuals(sz, genes):
individuals = []
for _ in range(sz):
individuals.append(Individual(sample(genes, len(genes))))
return Population(individuals)
def add(self, route):
self.individuals.append(route)
def rmv(self, route):
self.individuals.remove(route)
def get_fittest(self):
fittest = self.individuals[0]
for route in self.individuals:
if route.fitness > fittest.fitness:
fittest = route
return fittest
def evolve(pop, tourn_size, mut_rate):
new_generation = Population([])
pop_size = len(pop.individuals)
elitism_num = pop_size // 2
# Elitism
for _ in range(elitism_num):
fittest = pop.get_fittest()
new_generation.add(fittest)
pop.rmv(fittest)
# Crossover
for _ in range(elitism_num, pop_size):
parent_1 = selection(new_generation, tourn_size)
parent_2 = selection(new_generation, tourn_size)
child = crossover(parent_1, parent_2)
new_generation.add(child)
9
# Mutation
for i in range(elitism_num, pop_size):
mutate(new_generation.individuals[i], mut_rate)
return new_generation
def crossover(parent_1, parent_2):
def fill_with_parent1_genes(child, parent, genes_n):
start_at = randint(0, len(parent.genes)-genes_n-1)
finish_at = start_at + genes_n
for i in range(start_at, finish_at):
child.genes[i] = parent_1.genes[i]
def fill_with_parent2_genes(child, parent):
j = 0
for i in range(0, len(parent.genes)):
if child.genes[i] == None:
while parent.genes[j] in child.genes:
j += 1
child.genes[i] = parent.genes[j]
j += 1
genes_n = len(parent_1.genes)
child = Individual([None for _ in range(genes_n)])
fill_with_parent1_genes(child, parent_1, genes_n // 2)
fill_with_parent2_genes(child, parent_2)
return child
def mutate(individual, rate):
for _ in range(len(individual.genes)):
if random() < rate:
sel_genes = sample(individual.genes, 2)
individual.swap(sel_genes[0], sel_genes[1])
def selection(population, competitors_n):
return Population(sample(population.individuals, competitors_n)).get_fittest(
)
def run_ga(genes, pop_size, n_gen, tourn_size, mut_rate, verbose=1):
population = Population.gen_individuals(pop_size, genes)
history = {'cost': [population.get_fittest().travel_cost]}
counter, generations, min_cost = 0, 0, maxsize
10
if verbose:
print("-- TSP-GA -- Initiating evolution...")
start_time = time()
while counter < n_gen:
population = evolve(population, tourn_size, mut_rate)
cost = population.get_fittest().travel_cost
if cost < min_cost:
counter, min_cost = 0, cost
else:
counter += 1
generations += 1
history['cost'].append(cost)
total_time = round(time() - start_time, 6)
if verbose:
print("-- TSP-GA -- Evolution finished after {} generations in {} s".for
mat(generations, total_time))
print("-- TSP-GA -- Minimum travelling cost {} KM".format(min_cost))
history['generations'] = generations
history['total_time'] = total_time
history['route'] = population.get_fittest()
return history
from time import time
from random import random, randint, sample
from haversine import haversine
class Gene: # City
# keep distances from cities saved in a table to improve execution time.
__distances_table = {}
def __init__(self, name, lat, lng):
self.name = name
self.lat = lat
self.lng = lng
def get_distance_to(self, dest):
origin = (self.lat, self.lng)
11
dest = (dest.lat, dest.lng)
forward_key = origin + dest
backward_key = dest + origin
if forward_key in Gene.__distances_table:
return Gene.__distances_table[forward_key]
if backward_key in Gene.__distances_table:
return Gene.__distances_table[backward_key]
dist = int(haversine(origin, dest))
Gene.__distances_table[forward_key] = dist
return dist
class Individual: # Route: possible solution to TSP
def __init__(self, genes):
assert(len(genes) > 3)
self.genes = genes
self.__reset_params()
def swap(self, gene_1, gene_2):
self.genes[0]
a, b = self.genes.index(gene_1), self.genes.index(gene_2)
self.genes[b], self.genes[a] = self.genes[a], self.genes[b]
self.__reset_params()
def add(self, gene):
self.genes.append(gene)
self.__reset_params()
@property
def fitness(self):
if self.__fitness == 0:
self.__fitness = 1 / self.travel_cost # Normalize travel cost
return self.__fitness
@property
def travel_cost(self): # Get total travelling cost
if self.__travel_cost == 0:
for i in range(len(self.genes)):
origin = self.genes[i]
if i == len(self.genes) - 1:
12
dest = self.genes[0]
else:
dest = self.genes[i+1]
self.__travel_cost += origin.get_distance_to(dest)
return self.__travel_cost
def __reset_params(self):
self.__travel_cost = 0
self.__fitness = 0
class Population: # Population of individuals
def __init__(self, individuals):
self.individuals = individuals
@staticmethod
def gen_individuals(sz, genes):
individuals = []
for _ in range(sz):
individuals.append(Individual(sample(genes, len(genes))))
return Population(individuals)
def add(self, route):
self.individuals.append(route)
def rmv(self, route):
self.individuals.remove(route)
def get_fittest(self):
fittest = self.individuals[0]
for route in self.individuals:
if route.fitness > fittest.fitness:
fittest = route
return fittest
def evolve(pop, tourn_size, mut_rate):
new_generation = Population([])
pop_size = len(pop.individuals)
elitism_num = pop_size // 2
# Elitism
13
for _ in range(elitism_num):
fittest = pop.get_fittest()
new_generation.add(fittest)
pop.rmv(fittest)
# Crossover
for _ in range(elitism_num, pop_size):
parent_1 = selection(new_generation, tourn_size)
parent_2 = selection(new_generation, tourn_size)
child = crossover(parent_1, parent_2)
new_generation.add(child)
# Mutation
for i in range(elitism_num, pop_size):
mutate(new_generation.individuals[i], mut_rate)
return new_generation
def crossover(parent_1, parent_2):
def fill_with_parent1_genes(child, parent, genes_n):
start_at = randint(0, len(parent.genes)-genes_n-1)
finish_at = start_at + genes_n
for i in range(start_at, finish_at):
child.genes[i] = parent_1.genes[i]
def fill_with_parent2_genes(child, parent):
j = 0
for i in range(0, len(parent.genes)):
if child.genes[i] == None:
while parent.genes[j] in child.genes:
j += 1
child.genes[i] = parent.genes[j]
j += 1
genes_n = len(parent_1.genes)
child = Individual([None for _ in range(genes_n)])
fill_with_parent1_genes(child, parent_1, genes_n // 2)
fill_with_parent2_genes(child, parent_2)
return child
def mutate(individual, rate):
for _ in range(len(individual.genes)):
14
if random() < rate:
sel_genes = sample(individual.genes, 2)
individual.swap(sel_genes[0], sel_genes[1])
def selection(population, competitors_n):
return Population(sample(population.individuals, competitors_n)).get_fittest(
)
def run_ga(genes, pop_size, n_gen, tourn_size, mut_rate, verbose=1):
population = Population.gen_individuals(pop_size, genes)
history = {'cost': [population.get_fittest().travel_cost]}
counter, generations, min_cost = 0, 0, maxsize
if verbose:
print("-- TSP-GA -- Initiating evolution...")
start_time = time()
while counter < n_gen:
population = evolve(population, tourn_size, mut_rate)
cost = population.get_fittest().travel_cost
if cost < min_cost:
counter, min_cost = 0, cost
else:
counter += 1
generations += 1
history['cost'].append(cost)
total_time = round(time() - start_time, 6)
if verbose:
print("-- TSP-GA -- Evolution finished after {} generations in {} s
".format(generations, total_time))
print("-- TSP-GA -- Minimum travelling cost {} KM".format(min_cost))
history['generations'] = generations
history['total_time'] = total_time
history['route'] = population.get_fittest()
return history
2. main.py
15
import utils
import random
import argparse
import tsp_ga as ga
from datetime import datetime
def run(args):
genes = utils.get_genes_from(args.cities_fn)
if args.verbose:
print("-- Running TSP-GA with {} cities --".format(len(genes)))
history = ga.run_ga(genes, args.pop_size, args.n_gen,
args.tourn_size, args.mut_rate, args.verbose)
if args.verbose:
print("-- Drawing Route --")
utils.plot(history['cost'], history['route'])
if args.verbose:
print("-- Done --")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('-v', '--verbose', type=int, default=1)
parser.add_argument('--pop_size', type=int, default=500, help='Population
size')
parser.add_argument('--tourn_size', type=int, default=50, hel
p='Tournament size')
parser.add_argument('--mut_rate', type=float, default=0.02, hel
p='Mutation rate')
parser.add_argument('--n_gen', type=int, default=20, help='Number of equa
l generations before stopping')
parser.add_argument('--cities_fn', type=str, default="data/cities.csv", h
elp='Data containing the geographical coordinates of cities')
random.seed(datetime.now())
args = parser.parse_args()
if args.tourn_size > args.pop_size:
16
raise argparse.ArgumentTypeError('Tournament size cannot be bigger th
an population size.')
run(args)
Screen Shots:
17
Conclusion:
Combining the knowledge from heuristic methods and genetic algorithms is a promising
approach for solving the TSP. Genetic algorithms appear to find good solutions for the travelling
salesman problem, however it depends very much on the way the problem is encoded and which
crossover and mutation methods are used. genetic algorithm results in nearest optimal solution
within a reasonable time.
Reference:
1. www.towordsdatascience.com
2. www.geeksforgeeks.com
3. www.w3school.com
18
19