Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/genetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import sys
import statistics


def _generate_parent(length, gene_set, get_fitness):
# Generate a parent with random sampling of genes from gene set
genes = []
Expand Down
131 changes: 131 additions & 0 deletions src/test/test_traveling_salesman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import unittest
import os
import datetime

from src import genetic, traveling_salesman


class TSPTests(unittest.TestCase):
def test_five_cities(self):
city_a = traveling_salesman.Node('A')
city_b = traveling_salesman.Node('B')
city_c = traveling_salesman.Node('C')
city_d = traveling_salesman.Node('D')
city_e = traveling_salesman.Node('E')

city_a.add_edge(city_b, 16)
city_a.add_edge(city_e, 22)
city_a.add_edge(city_d, 14)
city_b.add_edge(city_e, 4)
city_b.add_edge(city_c, 18)
city_d.add_edge(city_c, 19)
city_d.add_edge(city_e, 7)

self.find_cheapest_route(nodes=[
city_a, city_b, city_c,
city_d, city_e
], optimal_cost=-43)

def test_four_cities(self):
city_a = traveling_salesman.Node('A')
city_b = traveling_salesman.Node('B')
city_c = traveling_salesman.Node('C')
city_d = traveling_salesman.Node('D')

city_a.add_edge(city_b, 3)
city_a.add_edge(city_c, 5)
city_a.add_edge(city_d, 2)
city_b.add_edge(city_c, 4)
city_b.add_edge(city_d, 6)
city_c.add_edge(city_d, 2)

nodes = [city_a, city_b, city_c, city_d]
self.find_cheapest_route(nodes, optimal_cost=-7)

def find_cheapest_route(self, nodes, optimal_cost):
print('Defined Map:')
for city in nodes:
print('{0} -> {1}'.format(city, ', '.join(map(str, city.linked_cities))))
print('')

# Okay, now we have 5 cities, each connected to all the other cities with a random cost 1-10

gene_set = nodes[:]
start_time = datetime.datetime.now()

def fn_display(candidate):
traveling_salesman.display(candidate, start_time)

def fn_get_fitness(genes):
return traveling_salesman.get_fitness(genes, gene_set)

def fn_mutate(genes):
traveling_salesman.mutate(genes, gene_set)

best = genetic.get_best(
get_fitness=fn_get_fitness,
target_len=len(gene_set),
optimal_fitness=optimal_cost,
gene_set=gene_set,
display=fn_display,
custom_mutate=fn_mutate
)
self.assertTrue(not optimal_cost > best.fitness)

def test_benchmark(self):
runs = 100
if os.environ.get('MINIMAL_BENCHMARK_TESTS', False):
runs = 1
genetic.Benchmark.run(lambda: self.test_five_cities(), runs=runs)

def test_create_node(self):
city_a = traveling_salesman.Node('A')
city_b = traveling_salesman.Node('B', linked_cities={city_a: 4})
self.assertEqual(city_a.value, 'A')
self.assertEqual(city_b.linked_cities[city_a], 4)

def test_node_eq_and_ne(self):
city_a = traveling_salesman.Node('A')
city_a_2 = traveling_salesman.Node('A')
city_b = traveling_salesman.Node('B')
self.assertEqual(city_a, city_a_2)
self.assertNotEqual(city_a, city_b)

def test_repr(self):
city_a = traveling_salesman.Node('A')
self.assertEqual(repr(city_a), str(city_a))

def test_get_fitness(self):
city_a = traveling_salesman.Node('A')
city_b = traveling_salesman.Node('B')
city_a.add_edge(city_b, 5)

# Test it without duplicates
best_fitness = traveling_salesman.get_fitness([city_a, city_b], [city_a, city_b])
self.assertEqual(best_fitness, -5)

# Test it with duplicates
bad_fitness = traveling_salesman.get_fitness([city_a, city_a], [city_a, city_b])
self.assertTrue(bad_fitness < best_fitness)

print('Fitness w/o duplicates: {0}\nFitness w/ duplicates: {1}'.format(
best_fitness, bad_fitness
))

def test_mutation_without_duplicates(self):
city_a = traveling_salesman.Node('A')
city_b = traveling_salesman.Node('B')
city_a.add_edge(city_b, 5)
genes = [city_a, city_b] # A -> B
gene_set = [city_a, city_b]

traveling_salesman.mutate(genes, gene_set)

def test_mutation_with_duplicates(self):
city_a = traveling_salesman.Node('A')
city_b = traveling_salesman.Node('B')
city_a.add_edge(city_b, 5)
genes = [city_a, city_a]
gene_set = [city_a, city_b]

traveling_salesman.mutate(genes, gene_set)
79 changes: 79 additions & 0 deletions src/traveling_salesman.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

import datetime
import random

from src import genetic


class Node(object):
def __init__(self, value, linked_cities=None):
self.value = value
if linked_cities is None:
self.linked_cities = dict()
else:
self.linked_cities = linked_cities

def add_edge(self, other_node, cost):
if other_node in self.linked_cities:
return
self.linked_cities[other_node] = cost
other_node.add_edge(self, cost)

def __ne__(self, other):
return self.value != other.value

def __eq__(self, other):
return self.value == other.value

def __str__(self):
return str(self.value)

def __repr__(self):
return self.__str__()

def __hash__(self):
return hash(self.value)


def get_cost(genes):
prev_node = genes[0]
total_cost = 0

for node in genes[1:]:
total_cost += prev_node.linked_cities.get(node, 1000)
prev_node = node

return total_cost


def get_fitness(genes, gene_set):
fitness = -1 * get_cost(genes) # we use a different scale where 0 is the max and poorer fitnesses are < 0
num_duplicates = len(genes) - len(set(genes))
if num_duplicates > 0:
fitness -= num_duplicates * 1000
fitness -= (len(gene_set) - len(set(genes))) * 1000 # also subtract number of missing
return fitness


def display(candidate: genetic.Chromosome, start_time):
time_diff = datetime.datetime.now() - start_time
print('{genes}\t\t{fitness}\t\t{timing}'.format(
genes=' -> '.join(map(str, candidate.genes)),
fitness=abs(candidate.fitness),
timing=str(time_diff)
))


def mutate(genes, gene_set, shuffle_chance=0.1):
if len(genes) == len(set(genes)):
mut_index_1 = random.randrange(0, len(genes))
mut_index_2 = random.randrange(0, len(genes))
genes[mut_index_1], genes[mut_index_2] = genes[mut_index_2], genes[mut_index_1]
else:
count = random.randint(1, 4)
for i in range(count):
genes[random.randrange(0, len(genes))] = random.choice(gene_set)

if random.random() <= shuffle_chance:
random.shuffle(genes)