com2014-template/Workshop - 2 (UCS, A, Hill-...

25 KiB

None <html> <head> </head>

Make sure you run this at the begining

In [ ]:
import os
import sys
import math
import numpy as np
import matplotlib.pyplot as plt

# Append template path to sys path
sys.path.append(os.getcwd() + "/template")
In [ ]:
from utils.load_data import load_data
from utils.visualize_tsp import plotTSP

from tsp import TSP_Bench_ONE
from tsp import TSP_Bench_PATH
from tsp import TSP_Bench_ALL

Workshop Starts Here

TSP
solutions

Get familiar with your dataset

There are problems at different levels. 3 easy, 2 medium, 1 difficult.

In [ ]:
for root, _, files in os.walk('./template/data'):
    if(files):
        for f in files:
            print(str(root) + "/" + f)
In [ ]:
ulysses16 = np.array(load_data("./template/data/easy/ulysses16.tsp"))
In [ ]:
ulysses16[:]
In [ ]:
plt.scatter(ulysses16[:, 0], ulysses16[:, 1])
for i in range(0, 16):
    plt.annotate(i, (ulysses16[i, 0], ulysses16[i, 1]+0.5))

Naive Solution: In Order

In [ ]:
simple_sequence = list(range(0, 16))
print(simple_sequence)
In [ ]:
plotTSP([simple_sequence], ulysses16, num_iters=1)

Naive Solution: Random Permutation

In [ ]:
random_permutation = np.random.permutation(16).tolist()
print(random_permutation)
In [ ]:
plotTSP([random_permutation], ulysses16, num_iters=1)

Best Solution

In [ ]:
best_ulysses16 = [0, 13, 12, 11, 6, 5, 14, 4, 10, 8, 9, 15, 2, 1, 3, 7]
plotTSP([best_ulysses16], ulysses16, num_iters=1)

Calculate Fitness (Sum of all Distances)

In [ ]:
def dist(node_0, node_1, coords):
        """
        Euclidean distance between two nodes.
        """
        coord_0, coord_1 = coords[node_0], coords[node_1]
        return math.sqrt((coord_0[0] - coord_1[0]) ** 2 + (coord_0[1] - coord_1[1]) ** 2)
In [ ]:
print("Coordinate of City 0:", ulysses16[0])
In [ ]:
print("Coordinate of City 1:", ulysses16[1])
In [ ]:
print("Distance Between", dist(0, 1, ulysses16))
In [ ]:
def fitness(solution, coords):
    N = len(coords)
    cur_fit = 0
    if(len(solution) != N):
        return math.inf
    for i in range(len(solution)):
        cur_fit += dist(solution[i % N], solution[(i + 1) % N], coords)
    return cur_fit
In [ ]:
print ("Order Fitness:\t", fitness(simple_sequence, ulysses16))
print ("Random Fitness:\t", fitness(random_permutation, ulysses16))
print ("Best Fitness:\t", fitness(best_ulysses16, ulysses16))

Naive Random Model

In [ ]:
import math
import random
from model.base_model import Model
import numpy as np

class MyRandomModel(Model):
    def __init__(self):
        super().__init__()

    def init(self, nodes):
        """
        Put your initialization here.
        """
        super().init(nodes)

    def fit(self, max_it=1000):
        """
        Put your iteration process here.
        """
        random_solutions = []
        for i in range(0, max_it):
            solution = np.random.permutation(self.N).tolist()
            random_solutions.append(solution)
            self.fitness_list.append(self.fitness(solution))

        self.best_solution = random_solutions[self.fitness_list.index(min(self.fitness_list))]
        return self.best_solution, self.fitness_list
In [ ]:
tsp_file = './template/data/easy/ulysses16.tsp'
In [ ]:
best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyRandomModel)
In [ ]:
plt.plot(fitness_list, 'o-')
In [ ]:
import math
import random
from model.base_model import Model

class MyUCSModel(Model):
    def __init__(self):
        super().__init__()

    def init(self, nodes):
        """
        Put your initialization here.
        """
        super().init(nodes)

    def getMST(self, node):
        MST = []
        distances = []
        for i in range(0, self.N):
            if i != node:
                MST.append(i)
                distances.append(self.dist(node, i))
        return [x for _,x in sorted(zip(distances, MST))]

    def fit(self, max_it=1000):
        """
        Put your iteration process here.
        """

        UCS_solutions = []
        UCS_losses = []
    
        for i in range(0, self.N):
            solution = [i]
            UCS_solutions.append(solution)
            UCS_losses.append(math.inf)
    
        MSTs = []
        for i in range(0, self.N):
            MSTs.append([-1] * self.N)

        # Breadth First: Set each city as starting point, then go to next city simultaneously
        min_loss = math.inf
        while(len(UCS_solutions[ UCS_losses.index(min(UCS_losses)) ]) != self.N):
            unvisited_list = list(range(0, self.N))
            min_loss = min(UCS_losses)
            # For each search path
            for i in range(0, self.N):
                if UCS_losses[i] == min_loss:
                    cur_city = UCS_solutions[i][-1]
                    unvisited_list = list( set(range(0, self.N)) - set(UCS_solutions[i]) )
                    if MSTs[cur_city][0] == -1:
                        MST = self.getMST(cur_city)
                        MSTs[cur_city] = MST

                    for j in MSTs[cur_city]:
                        if(j in unvisited_list):
                            UCS_solutions[i].append(j)

                            N = len(UCS_solutions[i])
                            cur_fit = 0
                            for k in range(len(UCS_solutions[i])):
                                coord_0, coord_1 = self.coords[UCS_solutions[i][k % N]], self.coords[UCS_solutions[i][(k + 1) % N]]
                                cur_fit += math.sqrt((coord_0[0] - coord_1[0]) ** 2 + (coord_0[1] - coord_1[1]) ** 2)
                            UCS_losses[i] = cur_fit
                            # if(UCS_losses[i] < min_loss):
                                # min_loss = UCS_losses[i]
                            break
        self.best_solution = UCS_solutions[ UCS_losses.index(min(UCS_losses)) ]
        self.fitness_list.append(self.fitness(self.best_solution))

        return self.best_solution, self.fitness_list
In [ ]:
tsp_file = './template/data/easy/ulysses16.tsp'
In [ ]:
best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyUCSModel)

A star

Heuristic

In [ ]:
import math
import random
from model.base_model import Model

class MyASModel(Model):
    def __init__(self):
        super().__init__()

    def init(self, nodes):
        """
        Put your initialization here.
        """
        super().init(nodes)

    def getMST(self, node):
        MST = []
        distances = []
        for i in range(0, self.N):
            if i != node:
                MST.append(i)
                distances.append(self.dist(node, i))
        return [x for _,x in sorted(zip(distances, MST))]

    def fit(self, max_it=1000):
        """
        Put your iteration process here.
        """

        MST_solutions = []
    
        for i in range(0, self.N):
            solution = [i]
            MST_solutions.append(solution)
    
        # Breadth First: Set each city as starting point, then go to next city simultaneously
        for step in range(0, self.N - 1):
            # print("[step]", step)
            unvisited_list = list(range(0, self.N))
            # For each search path
            for i in range(0, self.N):
                cur_city = MST_solutions[i][-1]
                unvisited_list = list( set(range(0, self.N)) - set(MST_solutions[i]) )
                closest_neighbour = -1
                min_f = math.inf
                    
                for j in unvisited_list:
                    g = self.dist(cur_city, j)
                    sub_unvisited_list = unvisited_list.copy()
                    sub_unvisited_list.remove(j)

                    sub_cur_city = self.getMST(j)[0]
                    h = 0
                    while len(sub_unvisited_list) > 0:
                        if(len(unvisited_list) == 2):
                            break
                        else:
                            for k in self.getMST(sub_cur_city):
                                if k in sub_unvisited_list:
                                    h = h + self.dist(sub_cur_city, k)
                                    sub_cur_city = k
                                    sub_unvisited_list.remove(k)
                                    break
                    # Get f(x) = g(x) + h(x)

                    f = g + h
                    if(f < min_f):
                        closest_neighbour = j
                        min_f = f
                MST_solutions[i].append(closest_neighbour)

        for i in range(0, self.N):
            self.fitness_list.append(self.fitness(MST_solutions[i]))
 
        self.best_solution = MST_solutions[ self.fitness_list.index(min(self.fitness_list)) ]

        return self.best_solution, self.fitness_list
In [ ]:
tsp_file = './template/data/easy/ulysses16.tsp'
In [ ]:
best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyASModel)
In [ ]:
plt.plot(fitness_list, 'o-')

Hill Climbing

Heuristic Iteration

In [ ]:
import math
import random
import numpy as np
from model.base_model import Model

class MyHillClimbModel(Model):

    def __init__(self):
        super().__init__()

    def init(self, nodes):
        """
        Put your initialization here.
        """
        super().init(nodes)

    def random_tour(self):
        return np.random.permutation(self.N).tolist()

    def all_pairs(self, size, shuffle=random.shuffle):
        r1 = list(range(size))
        r2 = list(range(size))
        if shuffle:
            shuffle(r1)
            shuffle(r2)
        for i in r1:
            for j in r2:
                yield (i,j)

    def move_operator(self, tour):
        '''generator to return all possible variations
          where the section between two cities are swapped'''
        for i,j in self.all_pairs(len(tour)):
            if i != j:
                copy=tour[:]
                if i < j:
                    copy[i:j+1]=reversed(tour[i:j+1])
                else:
                    copy[i+1:]=reversed(tour[:j])
                    copy[:j]=reversed(tour[i+1:])
                if copy != tour: # no point returning the same tour
                    yield copy

    def fit(self, max_it=100):
        """
        Put your iteration process here.
        """

        self.best_solution = self.random_tour()
        best_score = -self.fitness(self.best_solution)

        num_evaluations = 0

        while num_evaluations < max_it:
            # examine moves around our current position
            move_made = False
            for next_solution in self.move_operator(self.best_solution):
                if num_evaluations >= max_it:
                    print("Max iteration reached:", max_it)
                    break

                # see if this move is better than the current
                next_score = -self.fitness(next_solution)
                num_evaluations += 1
                if next_score > best_score:
                    self.best_solution = next_solution
                    self.fitness_list.append(self.fitness(self.best_solution))
                    best_score=next_score
                    move_made=True
                    break # depth first search

            if not move_made:
                break # we couldn't find a better move (must be at a local maximum)

        return self.best_solution, self.fitness_list
In [ ]:
tsp_file = './template/data/easy/ulysses16.tsp'
In [ ]:
best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyHillClimbModel)
In [ ]:
plt.plot(fitness_list)

Your Smart Model

In [ ]:
import math
import random
from model.base_model import Model

class MyModel(Model):
    def __init__(self):
        super().__init__()

    def init(self, nodes):
        """
        Put your initialization here.
        """
        super().init(nodes)

        self.log("Nothing to initialize in your model now")

    def fit(self, max_it=1000):
        """
        Put your iteration process here.
        """
        self.best_solution = np.random.permutation(self.N).tolist()
        self.fitness_list.append(self.fitness(self.best_solution))

        return self.best_solution, self.fitness_list

Test your Model

In [ ]:
tsp_problem = './template/data/easy/ulysses16.tsp'
In [ ]:
best_solution, fitness_list, time = TSP_Bench_ONE(tsp_file, MyModel)

Test All Dataset

In [ ]:
tsp_path = './template'
for root, _, files in os.walk(tsp_path + '/data'):
    if(files):
        for f in files:
            print(str(root) + "/" + f)
In [ ]:
def plot_results(best_solutions, times, title):
    fig = plt.figure()
    nodes = [len(s) for s in best_solutions]
    data = np.array([[node, time] for node, time in sorted(zip(nodes, times))])
    plt.plot(data[:, 0], data[:, 1], 'o-')
    fig.suptitle(title, fontsize=20)
In [ ]:
print("Random Search")
best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyRandomModel)
In [ ]:
plot_results(best_solutions, times, "Random Model")
In [ ]:
print("Uniform Cost Search")
best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyUCSModel)
In [ ]:
plot_results(best_solutions, times, "Uniform Cost Search")
In [ ]:
print("A Star Search")
best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyASModel)
In [ ]:
plot_results(best_solutions, times, "A Star Search")
In [ ]:
print("Hill-Climbing Search")
best_solutions, fitness_lists, times = TSP_Bench_ALL(tsp_path, MyHillClimbModel)
In [ ]:
plot_results(best_solutions, times, "Hill-Climbing Search")

Conclusions

In [ ]:
# Easy
# ulysses16:  77.12 (UCS-BFS),     77.02 (A-Star)
# att48:      39236 (UCS-BFS),     47853 (A-Star)
# st70:         761 (UCS-BFS),  time-out (A-Star)

# Medium
# a280:       3088 (UCS-BFS),   time-out (A-Star)
# pcb442:    58952 (UCS-BFS),   time-out (A-Star)

# Difficult
# dsj1000: time-out (UCS-BFS), 542,620,572 (Hill-Climbing)

1. UCS is the slowest one, and gets the same result as BFS, DFS, DP

1. A-Star can only solve problems with number of cities < 50.

2. Hill-Climbing gets different results every time (Heuristic).

3. Hill-Climbing is the fastest one till now. (faster than Dynamic Programming, but worse results).

In [ ]:

</html>