###############################################################################
## TSP SOLUTION WITH GENETIC ALGORITHM ##
## Luke Collins ~ ICS2207 Assignment ##
###############################################################################
import sys, math, random
import numpy as np
import matplotlib.pyplot as plt
class Graph:
"""Graph data structure"""
def __init__(self, vertices):
self.vertices = vertices
self.n = len(vertices)
# Weighted adjacency matrix
self.a = np.empty((self.n, self.n))
self.a[:] = np.nan
def d(self, i, j):
"""Euclidean Metric d_2((x1, y1), (x2, y2))"""
# Check if the distance was computed before
if not np.isnan(self.a[i][j]):
return self.a[i][j]
_x1 = self.vertices[i][0]
_x2 = self.vertices[j][0]
_y1 = self.vertices[i][1]
_y2 = self.vertices[j][1]
# Otherwise compute it
_distance
= math.
sqrt((_x1
- _x2
)**2 + (_y1
- _y2
)**2)
# Add to matrix
self.a[i][j], self.a[j][i] = _distance, _distance
return _distance
def plot(self, tour=None):
"""Plots the cities and superimposes given tour"""
if tour is None:
tour = Tour(self, [])
_vertices = [self.vertices[0]]
for i in tour.vertices:
_vertices.append(self.vertices[i])
_vertices.append(self.vertices[0])
plt.title("Cost = " + str(tour.cost()))
plt.plot(*zip(*_vertices), '-r')
plt.scatter(*zip(*self.vertices), c="b", s=10, marker="s")
plt.show()
class Tour:
"""Tour data structure"""
def __init__(self, g, vertices = None):
"""Generate random tour in given graph g"""
self.g = g
if vertices is None:
self.vertices = list(range(1, g.n))
random.shuffle(self.vertices)
else:
self.vertices = vertices
self.__cost = None
def cost(self):
"""Return total edge-cost of tour"""
if self.__cost is None:
self.__cost = self.g.d(0, self.vertices[0]) + \
sum(map(self.g.d, self.vertices, self.vertices[1:])) + \
self.g.d(self.vertices[-1], 0)
return self.__cost
class GeneticAlgorithm:
"""Implements instance of genetic algorithm"""
def __init__(self, g, population_size, k=3, elite_mating_rate=0.3,
mutation_rate=0.1, mutation_swap_rate=0.5,
random_least_fit=0.1):
"""Initialises algorithm parameters"""
self.g = g
self.n = g.n
self.population = []
for _ in range(population_size):
self.population.append(Tour(g))
self.population_size = population_size
self.k = k
self.elite_mating_rate = elite_mating_rate
self.mutation_rate = mutation_rate
self.mutation_swap_rate = mutation_swap_rate
self.random_least_fit = random_least_fit
def crossover(self, mum, dad):
"""Implements ordered crossover"""
size = self.n - 1
# Choose random start/end position for crossover
start, end = sorted([random.randrange(size) for _ in range(2)])
# Identify the elements from mum's sequence which end up in alice,
# and from dad's which end up in bob
mumxo = set(mum.vertices[start:end + 1])
dadxo = set(dad.vertices[start:end + 1])
# Take the other elements in their original order
alice = [i for i in dad.vertices if not i in mumxo]
bob = [i for i in mum.vertices if not i in dadxo]
# Insert selected elements of mum's sequence for alice, dad's for bob
alice[start:start] = mum.vertices[start:end + 1]
bob[start:start] = dad.vertices[start:end + 1]
# Return twins
return Tour(self.g, alice), Tour(self.g, bob)
def mutate(self, tour):
"""Randomly swaps pairs of cities in a given tour according to mutation rate"""
# Decide whether to mutate
if random.random() < self.mutation_rate:
# For each vertex
for i in range(self.n - 1):
# Randomly decide whether to swap
if random.random() < self.mutation_swap_rate:
# Randomly choose other city position
j = random.randrange(self.n - 1)
# Swap
tour.vertices[i], tour.vertices[j] = tour.vertices[j], tour.vertices[i]
def select_parent(self, k):
"""Implements k-tournament selection to choose parents"""
tournament = random.sample(self.population, k)
return max(tournament, key=lambda t: t.cost())
def evolve(self):
"""Executes one iteration of the genetic algorithm to obtain a new generation"""
new_population = []
for _ in range(self.population_size):
# K-tournament for parents
mum, dad = self.select_parent(self.k), self.select_parent(self.k)
alice, bob = self.crossover(mum, dad)
# Mate in an elite fashion according to the elitism_rate
if random.random() < self.elite_mating_rate:
if alice.cost() < mum.cost() or alice.cost() < dad.cost():
new_population.append(alice)
if bob.cost() < mum.cost() or bob.cost() < dad.cost():
new_population.append(bob)
else:
self.mutate(alice)
self.mutate(bob)
new_population += [alice, bob]
# Add new population to old
self.population += new_population
# Retain fittest
self.population = sorted(self.population, key=lambda t: t.cost())[0:self.population_size]
def run(self, iterations):
"""Runs the GA until a pair of consecutive tours differ in cost by epsilon"""
for _ in range(iterations):
self.evolve()
# Replace least fit of population and replace with new random generated
# tours
retain = int((1-self.random_least_fit) * self.population_size)
to_add = self.population_size - retain
self.population = self.population[0:retain]
for _ in range(to_add):
self.population.append(Tour(self.g))
def best(self):
return min(self.population, key=lambda t: t.cost())
# Test on berlin52: http://e...content-available-to-author-only...b.de/pub/mp-testdata/tsp/tsplib/tsp/berlin52.tsp
g = Graph([(565.0, 575.0), (25.0, 185.0), (345.0, 750.0), (945.0, 685.0),
(845.0, 655.0), (880.0, 660.0), (25.0, 230.0), (525.0, 1000.0),
(580.0, 1175.0), (650.0, 1130.0), (1605.0, 620.0), (1220.0, 580.0),
(1465.0, 200.0), (1530.0, 5.0), (845.0, 680.0), (725.0, 370.0),
(145.0, 665.0), (415.0, 635.0), (510.0, 875.0), (560.0, 365.0),
(300.0, 465.0), (520.0, 585.0), (480.0, 415.0), (835.0, 625.0),
(975.0, 580.0), (1215.0, 245.0), (1320.0, 315.0), (1250.0, 400.0),
(660.0, 180.0), (410.0, 250.0), (420.0, 555.0), (575.0, 665.0),
(1150.0, 1160.0), (700.0, 580.0), (685.0, 595.0), (685.0, 610.0),
(770.0, 610.0), (795.0, 645.0), (720.0, 635.0), (760.0, 650.0),
(475.0, 960.0), (95.0, 260.0), (875.0, 920.0), (700.0, 500.0),
(555.0, 815.0), (830.0, 485.0), (1170.0, 65.0), (830.0, 610.0),
(605.0, 625.0), (595.0, 360.0), (1340.0, 725.0), (1740.0, 245.0)])
# Population size 200, 8000 iterations
ga = GeneticAlgorithm(g, 200)
ga.run(8000)
best_tour = ga.best()
g.plot(best_tour)
IyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIyAgICAgICAgICAgICAgICAgICBUU1AgU09MVVRJT04gV0lUSCBHRU5FVElDIEFMR09SSVRITSAgICAgICAgICAgICAgICAgICAgICMjCiMjICAgICAgICAgICAgICAgICAgICBMdWtlIENvbGxpbnMgfiBJQ1MyMjA3IEFzc2lnbm1lbnQgICAgICAgICAgICAgICAgICAgICAgIyMKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKaW1wb3J0IHN5cywgbWF0aCwgcmFuZG9tCmltcG9ydCBudW1weSBhcyBucAppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0CgpjbGFzcyBHcmFwaDoKICAgICIiIkdyYXBoIGRhdGEgc3RydWN0dXJlIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIHZlcnRpY2VzKToKICAgICAgICBzZWxmLnZlcnRpY2VzID0gdmVydGljZXMKICAgICAgICBzZWxmLm4gPSBsZW4odmVydGljZXMpCgogICAgICAgICMgV2VpZ2h0ZWQgYWRqYWNlbmN5IG1hdHJpeAogICAgICAgIHNlbGYuYSA9IG5wLmVtcHR5KChzZWxmLm4sIHNlbGYubikpCiAgICAgICAgc2VsZi5hWzpdID0gbnAubmFuCgogICAgZGVmIGQoc2VsZiwgaSwgaik6CiAgICAgICAgIiIiRXVjbGlkZWFuIE1ldHJpYyBkXzIoKHgxLCB5MSksICh4MiwgeTIpKSIiIgoKICAgICAgICAjIENoZWNrIGlmIHRoZSBkaXN0YW5jZSB3YXMgY29tcHV0ZWQgYmVmb3JlCiAgICAgICAgaWYgbm90IG5wLmlzbmFuKHNlbGYuYVtpXVtqXSk6CiAgICAgICAgICAgIHJldHVybiBzZWxmLmFbaV1bal0KCiAgICAgICAgX3gxID0gc2VsZi52ZXJ0aWNlc1tpXVswXQogICAgICAgIF94MiA9IHNlbGYudmVydGljZXNbal1bMF0KICAgICAgICBfeTEgPSBzZWxmLnZlcnRpY2VzW2ldWzFdCiAgICAgICAgX3kyID0gc2VsZi52ZXJ0aWNlc1tqXVsxXQoKICAgICAgICAjIE90aGVyd2lzZSBjb21wdXRlIGl0CiAgICAgICAgX2Rpc3RhbmNlID0gbWF0aC5zcXJ0KChfeDEgLSBfeDIpKioyICsgKF95MSAtIF95MikqKjIpCgogICAgICAgICMgQWRkIHRvIG1hdHJpeAogICAgICAgIHNlbGYuYVtpXVtqXSwgc2VsZi5hW2pdW2ldID0gX2Rpc3RhbmNlLCBfZGlzdGFuY2UKICAgICAgICByZXR1cm4gX2Rpc3RhbmNlCgogICAgZGVmIHBsb3Qoc2VsZiwgdG91cj1Ob25lKToKICAgICAgICAiIiJQbG90cyB0aGUgY2l0aWVzIGFuZCBzdXBlcmltcG9zZXMgZ2l2ZW4gdG91ciIiIgoKICAgICAgICBpZiB0b3VyIGlzIE5vbmU6CiAgICAgICAgICAgIHRvdXIgPSBUb3VyKHNlbGYsIFtdKQoKICAgICAgICBfdmVydGljZXMgPSBbc2VsZi52ZXJ0aWNlc1swXV0KCiAgICAgICAgZm9yIGkgaW4gdG91ci52ZXJ0aWNlczoKICAgICAgICAgICAgX3ZlcnRpY2VzLmFwcGVuZChzZWxmLnZlcnRpY2VzW2ldKQoKICAgICAgICBfdmVydGljZXMuYXBwZW5kKHNlbGYudmVydGljZXNbMF0pCgogICAgICAgIHBsdC50aXRsZSgiQ29zdCA9ICIgKyBzdHIodG91ci5jb3N0KCkpKQogICAgICAgIHBsdC5wbG90KCp6aXAoKl92ZXJ0aWNlcyksICctcicpCiAgICAgICAgcGx0LnNjYXR0ZXIoKnppcCgqc2VsZi52ZXJ0aWNlcyksIGM9ImIiLCBzPTEwLCBtYXJrZXI9InMiKQogICAgICAgIHBsdC5zaG93KCkKCmNsYXNzIFRvdXI6CiAgICAiIiJUb3VyIGRhdGEgc3RydWN0dXJlIiIiCgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGcsIHZlcnRpY2VzID0gTm9uZSk6CiAgICAgICAgIiIiR2VuZXJhdGUgcmFuZG9tIHRvdXIgaW4gZ2l2ZW4gZ3JhcGggZyIiIgoKICAgICAgICBzZWxmLmcgPSBnCgogICAgICAgIGlmIHZlcnRpY2VzIGlzIE5vbmU6CiAgICAgICAgICAgIHNlbGYudmVydGljZXMgPSBsaXN0KHJhbmdlKDEsIGcubikpCiAgICAgICAgICAgIHJhbmRvbS5zaHVmZmxlKHNlbGYudmVydGljZXMpCiAgICAgICAgZWxzZToKICAgICAgICAgICAgc2VsZi52ZXJ0aWNlcyA9IHZlcnRpY2VzCgogICAgICAgIHNlbGYuX19jb3N0ID0gTm9uZQoKICAgIGRlZiBjb3N0KHNlbGYpOgogICAgICAgICIiIlJldHVybiB0b3RhbCBlZGdlLWNvc3Qgb2YgdG91ciIiIgogICAgICAgIGlmIHNlbGYuX19jb3N0IGlzIE5vbmU6CiAgICAgICAgICAgIHNlbGYuX19jb3N0ID0gc2VsZi5nLmQoMCwgc2VsZi52ZXJ0aWNlc1swXSkgKyBcCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtKG1hcChzZWxmLmcuZCwgc2VsZi52ZXJ0aWNlcywgc2VsZi52ZXJ0aWNlc1sxOl0pKSArIFwKICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxmLmcuZChzZWxmLnZlcnRpY2VzWy0xXSwgMCkKICAgICAgICByZXR1cm4gc2VsZi5fX2Nvc3QKCgpjbGFzcyBHZW5ldGljQWxnb3JpdGhtOgogICAgIiIiSW1wbGVtZW50cyBpbnN0YW5jZSBvZiBnZW5ldGljIGFsZ29yaXRobSIiIgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBnLCBwb3B1bGF0aW9uX3NpemUsIGs9MywgZWxpdGVfbWF0aW5nX3JhdGU9MC4zLAogICAgICAgICAgICAgICAgIG11dGF0aW9uX3JhdGU9MC4xLCBtdXRhdGlvbl9zd2FwX3JhdGU9MC41LAogICAgICAgICAgICAgICAgIHJhbmRvbV9sZWFzdF9maXQ9MC4xKToKICAgICAgICAiIiJJbml0aWFsaXNlcyBhbGdvcml0aG0gcGFyYW1ldGVycyIiIgoKICAgICAgICBzZWxmLmcgPSBnCiAgICAgICAgc2VsZi5uID0gZy5uCgogICAgICAgIHNlbGYucG9wdWxhdGlvbiA9IFtdCiAgICAgICAgZm9yIF8gaW4gcmFuZ2UocG9wdWxhdGlvbl9zaXplKToKICAgICAgICAgICAgc2VsZi5wb3B1bGF0aW9uLmFwcGVuZChUb3VyKGcpKQoKICAgICAgICBzZWxmLnBvcHVsYXRpb25fc2l6ZSA9IHBvcHVsYXRpb25fc2l6ZQogICAgICAgIHNlbGYuayA9IGsKICAgICAgICBzZWxmLmVsaXRlX21hdGluZ19yYXRlID0gZWxpdGVfbWF0aW5nX3JhdGUKICAgICAgICBzZWxmLm11dGF0aW9uX3JhdGUgPSBtdXRhdGlvbl9yYXRlCiAgICAgICAgc2VsZi5tdXRhdGlvbl9zd2FwX3JhdGUgPSBtdXRhdGlvbl9zd2FwX3JhdGUKICAgICAgICBzZWxmLnJhbmRvbV9sZWFzdF9maXQgPSByYW5kb21fbGVhc3RfZml0CgogICAgZGVmIGNyb3Nzb3ZlcihzZWxmLCBtdW0sIGRhZCk6CiAgICAgICAgIiIiSW1wbGVtZW50cyBvcmRlcmVkIGNyb3Nzb3ZlciIiIgoKICAgICAgICBzaXplID0gc2VsZi5uIC0gMQoKICAgICAgICAjIENob29zZSByYW5kb20gc3RhcnQvZW5kIHBvc2l0aW9uIGZvciBjcm9zc292ZXIKICAgICAgICBzdGFydCwgZW5kID0gc29ydGVkKFtyYW5kb20ucmFuZHJhbmdlKHNpemUpIGZvciBfIGluIHJhbmdlKDIpXSkKCiAgICAgICAgIyBJZGVudGlmeSB0aGUgZWxlbWVudHMgZnJvbSBtdW0ncyBzZXF1ZW5jZSB3aGljaCBlbmQgdXAgaW4gYWxpY2UsCiAgICAgICAgIyBhbmQgZnJvbSBkYWQncyB3aGljaCBlbmQgdXAgaW4gYm9iCiAgICAgICAgbXVteG8gPSBzZXQobXVtLnZlcnRpY2VzW3N0YXJ0OmVuZCArIDFdKQogICAgICAgIGRhZHhvID0gc2V0KGRhZC52ZXJ0aWNlc1tzdGFydDplbmQgKyAxXSkKCiAgICAgICAgIyBUYWtlIHRoZSBvdGhlciBlbGVtZW50cyBpbiB0aGVpciBvcmlnaW5hbCBvcmRlcgogICAgICAgIGFsaWNlID0gW2kgZm9yIGkgaW4gZGFkLnZlcnRpY2VzIGlmIG5vdCBpIGluIG11bXhvXQogICAgICAgIGJvYiA9IFtpIGZvciBpIGluIG11bS52ZXJ0aWNlcyBpZiBub3QgaSBpbiBkYWR4b10KCiAgICAgICAgIyBJbnNlcnQgc2VsZWN0ZWQgZWxlbWVudHMgb2YgbXVtJ3Mgc2VxdWVuY2UgZm9yIGFsaWNlLCBkYWQncyBmb3IgYm9iCiAgICAgICAgYWxpY2Vbc3RhcnQ6c3RhcnRdID0gbXVtLnZlcnRpY2VzW3N0YXJ0OmVuZCArIDFdCiAgICAgICAgYm9iW3N0YXJ0OnN0YXJ0XSA9IGRhZC52ZXJ0aWNlc1tzdGFydDplbmQgKyAxXQoKICAgICAgICAjIFJldHVybiB0d2lucwogICAgICAgIHJldHVybiBUb3VyKHNlbGYuZywgYWxpY2UpLCBUb3VyKHNlbGYuZywgYm9iKQoKICAgIGRlZiBtdXRhdGUoc2VsZiwgdG91cik6CiAgICAgICAgIiIiUmFuZG9tbHkgc3dhcHMgcGFpcnMgb2YgY2l0aWVzIGluIGEgZ2l2ZW4gdG91ciBhY2NvcmRpbmcgdG8gbXV0YXRpb24gcmF0ZSIiIgoKICAgICAgICAjIERlY2lkZSB3aGV0aGVyIHRvIG11dGF0ZQogICAgICAgIGlmIHJhbmRvbS5yYW5kb20oKSA8IHNlbGYubXV0YXRpb25fcmF0ZToKCiAgICAgICAgICAgICMgRm9yIGVhY2ggdmVydGV4CiAgICAgICAgICAgIGZvciBpIGluIHJhbmdlKHNlbGYubiAtIDEpOgoKICAgICAgICAgICAgICAgICMgUmFuZG9tbHkgZGVjaWRlIHdoZXRoZXIgdG8gc3dhcAogICAgICAgICAgICAgICAgaWYgcmFuZG9tLnJhbmRvbSgpIDwgc2VsZi5tdXRhdGlvbl9zd2FwX3JhdGU6CgogICAgICAgICAgICAgICAgICAgICMgUmFuZG9tbHkgY2hvb3NlIG90aGVyIGNpdHkgcG9zaXRpb24KICAgICAgICAgICAgICAgICAgICBqID0gcmFuZG9tLnJhbmRyYW5nZShzZWxmLm4gLSAxKQoKICAgICAgICAgICAgICAgICAgICAjIFN3YXAKICAgICAgICAgICAgICAgICAgICB0b3VyLnZlcnRpY2VzW2ldLCB0b3VyLnZlcnRpY2VzW2pdID0gdG91ci52ZXJ0aWNlc1tqXSwgdG91ci52ZXJ0aWNlc1tpXQoKICAgIGRlZiBzZWxlY3RfcGFyZW50KHNlbGYsIGspOgogICAgICAgICIiIkltcGxlbWVudHMgay10b3VybmFtZW50IHNlbGVjdGlvbiB0byBjaG9vc2UgcGFyZW50cyIiIgogICAgICAgIHRvdXJuYW1lbnQgPSByYW5kb20uc2FtcGxlKHNlbGYucG9wdWxhdGlvbiwgaykKICAgICAgICByZXR1cm4gbWF4KHRvdXJuYW1lbnQsIGtleT1sYW1iZGEgdDogdC5jb3N0KCkpCgogICAgZGVmIGV2b2x2ZShzZWxmKToKICAgICAgICAiIiJFeGVjdXRlcyBvbmUgaXRlcmF0aW9uIG9mIHRoZSBnZW5ldGljIGFsZ29yaXRobSB0byBvYnRhaW4gYSBuZXcgZ2VuZXJhdGlvbiIiIgoKICAgICAgICBuZXdfcG9wdWxhdGlvbiA9IFtdCgogICAgICAgIGZvciBfIGluIHJhbmdlKHNlbGYucG9wdWxhdGlvbl9zaXplKToKCiAgICAgICAgICAgICMgSy10b3VybmFtZW50IGZvciBwYXJlbnRzCiAgICAgICAgICAgIG11bSwgZGFkID0gc2VsZi5zZWxlY3RfcGFyZW50KHNlbGYuayksIHNlbGYuc2VsZWN0X3BhcmVudChzZWxmLmspCiAgICAgICAgICAgIGFsaWNlLCBib2IgPSBzZWxmLmNyb3Nzb3ZlcihtdW0sIGRhZCkKCiAgICAgICAgICAgICMgTWF0ZSBpbiBhbiBlbGl0ZSBmYXNoaW9uIGFjY29yZGluZyB0byB0aGUgZWxpdGlzbV9yYXRlCiAgICAgICAgICAgIGlmIHJhbmRvbS5yYW5kb20oKSA8IHNlbGYuZWxpdGVfbWF0aW5nX3JhdGU6CiAgICAgICAgICAgICAgICBpZiBhbGljZS5jb3N0KCkgPCBtdW0uY29zdCgpIG9yIGFsaWNlLmNvc3QoKSA8IGRhZC5jb3N0KCk6CiAgICAgICAgICAgICAgICAgICAgbmV3X3BvcHVsYXRpb24uYXBwZW5kKGFsaWNlKQogICAgICAgICAgICAgICAgaWYgYm9iLmNvc3QoKSA8IG11bS5jb3N0KCkgb3IgYm9iLmNvc3QoKSA8IGRhZC5jb3N0KCk6CiAgICAgICAgICAgICAgICAgICAgbmV3X3BvcHVsYXRpb24uYXBwZW5kKGJvYikKCiAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICBzZWxmLm11dGF0ZShhbGljZSkKICAgICAgICAgICAgICAgIHNlbGYubXV0YXRlKGJvYikKICAgICAgICAgICAgICAgIG5ld19wb3B1bGF0aW9uICs9IFthbGljZSwgYm9iXQoKICAgICAgICAjIEFkZCBuZXcgcG9wdWxhdGlvbiB0byBvbGQKICAgICAgICBzZWxmLnBvcHVsYXRpb24gKz0gbmV3X3BvcHVsYXRpb24KCiAgICAgICAgIyBSZXRhaW4gZml0dGVzdAogICAgICAgIHNlbGYucG9wdWxhdGlvbiA9IHNvcnRlZChzZWxmLnBvcHVsYXRpb24sIGtleT1sYW1iZGEgdDogdC5jb3N0KCkpWzA6c2VsZi5wb3B1bGF0aW9uX3NpemVdCgogICAgZGVmIHJ1bihzZWxmLCBpdGVyYXRpb25zKToKICAgICAgICAiIiJSdW5zIHRoZSBHQSB1bnRpbCBhIHBhaXIgb2YgY29uc2VjdXRpdmUgdG91cnMgZGlmZmVyIGluIGNvc3QgYnkgZXBzaWxvbiIiIgogICAgICAgIGZvciBfIGluIHJhbmdlKGl0ZXJhdGlvbnMpOgogICAgICAgICAgICBzZWxmLmV2b2x2ZSgpCgogICAgICAgICAgICAjIFJlcGxhY2UgbGVhc3QgZml0IG9mIHBvcHVsYXRpb24gYW5kIHJlcGxhY2Ugd2l0aCBuZXcgcmFuZG9tIGdlbmVyYXRlZAogICAgICAgICAgICAjIHRvdXJzCiAgICAgICAgICAgIHJldGFpbiA9IGludCgoMS1zZWxmLnJhbmRvbV9sZWFzdF9maXQpICogc2VsZi5wb3B1bGF0aW9uX3NpemUpCiAgICAgICAgICAgIHRvX2FkZCA9IHNlbGYucG9wdWxhdGlvbl9zaXplIC0gcmV0YWluCiAgICAgICAgICAgIHNlbGYucG9wdWxhdGlvbiA9IHNlbGYucG9wdWxhdGlvblswOnJldGFpbl0KICAgICAgICAgICAgZm9yIF8gaW4gcmFuZ2UodG9fYWRkKToKICAgICAgICAgICAgICAgIHNlbGYucG9wdWxhdGlvbi5hcHBlbmQoVG91cihzZWxmLmcpKQoKICAgIGRlZiBiZXN0KHNlbGYpOgogICAgICAgIHJldHVybiBtaW4oc2VsZi5wb3B1bGF0aW9uLCBrZXk9bGFtYmRhIHQ6IHQuY29zdCgpKQoKIyBUZXN0IG9uIGJlcmxpbjUyOiBodHRwOi8vZS4uLmNvbnRlbnQtYXZhaWxhYmxlLXRvLWF1dGhvci1vbmx5Li4uYi5kZS9wdWIvbXAtdGVzdGRhdGEvdHNwL3RzcGxpYi90c3AvYmVybGluNTIudHNwCmcgPSBHcmFwaChbKDU2NS4wLCA1NzUuMCksICgyNS4wLCAxODUuMCksICgzNDUuMCwgNzUwLjApLCAoOTQ1LjAsIDY4NS4wKSwgCiAgICAgICAgICAgKDg0NS4wLCA2NTUuMCksICg4ODAuMCwgNjYwLjApLCAoMjUuMCwgMjMwLjApLCAoNTI1LjAsIDEwMDAuMCksIAogICAgICAgICAgICg1ODAuMCwgMTE3NS4wKSwgKDY1MC4wLCAxMTMwLjApLCAoMTYwNS4wLCA2MjAuMCksICgxMjIwLjAsIDU4MC4wKSwgCiAgICAgICAgICAgKDE0NjUuMCwgMjAwLjApLCAoMTUzMC4wLCA1LjApLCAoODQ1LjAsIDY4MC4wKSwgKDcyNS4wLCAzNzAuMCksIAogICAgICAgICAgICgxNDUuMCwgNjY1LjApLCAoNDE1LjAsIDYzNS4wKSwgKDUxMC4wLCA4NzUuMCksICg1NjAuMCwgMzY1LjApLCAKICAgICAgICAgICAoMzAwLjAsIDQ2NS4wKSwgKDUyMC4wLCA1ODUuMCksICg0ODAuMCwgNDE1LjApLCAoODM1LjAsIDYyNS4wKSwgCiAgICAgICAgICAgKDk3NS4wLCA1ODAuMCksICgxMjE1LjAsIDI0NS4wKSwgKDEzMjAuMCwgMzE1LjApLCAoMTI1MC4wLCA0MDAuMCksIAogICAgICAgICAgICg2NjAuMCwgMTgwLjApLCAoNDEwLjAsIDI1MC4wKSwgKDQyMC4wLCA1NTUuMCksICg1NzUuMCwgNjY1LjApLCAKICAgICAgICAgICAoMTE1MC4wLCAxMTYwLjApLCAoNzAwLjAsIDU4MC4wKSwgKDY4NS4wLCA1OTUuMCksICg2ODUuMCwgNjEwLjApLCAKICAgICAgICAgICAoNzcwLjAsIDYxMC4wKSwgKDc5NS4wLCA2NDUuMCksICg3MjAuMCwgNjM1LjApLCAoNzYwLjAsIDY1MC4wKSwgCiAgICAgICAgICAgKDQ3NS4wLCA5NjAuMCksICg5NS4wLCAyNjAuMCksICg4NzUuMCwgOTIwLjApLCAoNzAwLjAsIDUwMC4wKSwgCiAgICAgICAgICAgKDU1NS4wLCA4MTUuMCksICg4MzAuMCwgNDg1LjApLCAoMTE3MC4wLCA2NS4wKSwgKDgzMC4wLCA2MTAuMCksIAogICAgICAgICAgICg2MDUuMCwgNjI1LjApLCAoNTk1LjAsIDM2MC4wKSwgKDEzNDAuMCwgNzI1LjApLCAoMTc0MC4wLCAyNDUuMCldKQoKIyBQb3B1bGF0aW9uIHNpemUgMjAwLCA4MDAwIGl0ZXJhdGlvbnMKZ2EgPSBHZW5ldGljQWxnb3JpdGhtKGcsIDIwMCkKZ2EucnVuKDgwMDApCgpiZXN0X3RvdXIgPSBnYS5iZXN0KCkKZy5wbG90KGJlc3RfdG91cik=