Initial Commit
This commit is contained in:
83
model/anneal_model.py
Normal file
83
model/anneal_model.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import math
|
||||
import random
|
||||
import matplotlib.pyplot as plt
|
||||
from model.base_model import Model
|
||||
|
||||
class SimAnneal(Model):
|
||||
def __init__(self, coords, T=-1, alpha=-1, stopping_T=-1):
|
||||
super().__init__(coords)
|
||||
|
||||
self.iteration = 0
|
||||
|
||||
self.T = math.sqrt(self.N) if T == -1 else T
|
||||
self.T_save = self.T # save inital T to reset if batch annealing is used
|
||||
self.alpha = 0.995 if alpha == -1 else alpha
|
||||
self.stopping_temperature = 1e-8 if stopping_T == -1 else stopping_T
|
||||
|
||||
def initial_solution(self):
|
||||
"""
|
||||
Greedy algorithm to get an initial solution (closest-neighbour).
|
||||
"""
|
||||
cur_node = random.choice(self.nodes) # start from a random node
|
||||
solution = [cur_node]
|
||||
|
||||
free_nodes = set(self.nodes)
|
||||
free_nodes.remove(cur_node)
|
||||
while free_nodes:
|
||||
next_node = min(free_nodes, key=lambda x: self.dist(cur_node, x)) # nearest neighbour
|
||||
free_nodes.remove(next_node)
|
||||
solution.append(next_node)
|
||||
cur_node = next_node
|
||||
|
||||
cur_fit = self.fitness(solution)
|
||||
if cur_fit < self.best_fitness: # If best found so far, update best fitness
|
||||
self.best_fitness = cur_fit
|
||||
self.best_solution = solution
|
||||
self.fitness_list.append(cur_fit)
|
||||
return solution, cur_fit
|
||||
|
||||
def p_accept(self, candidate_fitness):
|
||||
"""
|
||||
Probability of accepting if the candidate is worse than current.
|
||||
Depends on the current temperature and difference between candidate and current.
|
||||
"""
|
||||
return math.exp(-abs(candidate_fitness - self.cur_fitness) / self.T)
|
||||
|
||||
def accept(self, candidate):
|
||||
"""
|
||||
Accept with probability 1 if candidate is better than current.
|
||||
Accept with probabilty p_accept(..) if candidate is worse.
|
||||
"""
|
||||
candidate_fitness = self.fitness(candidate)
|
||||
if candidate_fitness < self.cur_fitness:
|
||||
self.cur_fitness, self.cur_solution = candidate_fitness, candidate
|
||||
if candidate_fitness < self.best_fitness:
|
||||
self.best_fitness, self.best_solution = candidate_fitness, candidate
|
||||
else:
|
||||
if random.random() < self.p_accept(candidate_fitness):
|
||||
self.cur_fitness, self.cur_solution = candidate_fitness, candidate
|
||||
|
||||
def fit(self, max_it=1000):
|
||||
"""
|
||||
Execute simulated annealing algorithm.
|
||||
"""
|
||||
# Initialize with the greedy solution.
|
||||
self.cur_solution, self.cur_fitness = self.initial_solution()
|
||||
|
||||
self.log("Starting annealing.")
|
||||
while self.T >= self.stopping_temperature and self.iteration < max_it:
|
||||
candidate = list(self.cur_solution)
|
||||
l = random.randint(2, self.N - 1)
|
||||
i = random.randint(0, self.N - l)
|
||||
candidate[i : (i + l)] = reversed(candidate[i : (i + l)])
|
||||
self.accept(candidate)
|
||||
self.T *= self.alpha
|
||||
self.iteration += 1
|
||||
|
||||
self.fitness_list.append(self.cur_fitness)
|
||||
|
||||
self.log(f"Best fitness obtained: {self.best_fitness}")
|
||||
improvement = 100 * (self.fitness_list[0] - self.best_fitness) / (self.fitness_list[0])
|
||||
self.log(f"Improvement over greedy heuristic: {improvement : .2f}%")
|
||||
|
||||
return self.best_solution, self.fitness_list
|
||||
34
model/base_model.py
Normal file
34
model/base_model.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import math
|
||||
|
||||
class Model:
|
||||
|
||||
def __init__(self, coords):
|
||||
self.coords = coords
|
||||
self.N = len(coords)
|
||||
self.nodes = [i for i in range(self.N)]
|
||||
|
||||
self.best_solution = None
|
||||
self.best_fitness = float("Inf")
|
||||
self.fitness_list = []
|
||||
|
||||
def dist(self, node_0, node_1):
|
||||
"""
|
||||
Euclidean distance between two nodes.
|
||||
"""
|
||||
coord_0, coord_1 = self.coords[node_0], self.coords[node_1]
|
||||
return math.sqrt((coord_0[0] - coord_1[0]) ** 2 + (coord_0[1] - coord_1[1]) ** 2)
|
||||
|
||||
def fitness(self, solution):
|
||||
"""
|
||||
Total distance of the current solution path.
|
||||
"""
|
||||
cur_fit = 0
|
||||
for i in range(self.N):
|
||||
cur_fit += self.dist(solution[i % self.N], solution[(i + 1) % self.N])
|
||||
return cur_fit
|
||||
|
||||
def fit(self):
|
||||
raise NotImplementedError("Your fitting method not implemented yet")
|
||||
|
||||
def log(self, message):
|
||||
print('[{name}] {msg}'.format(name=self.__class__.__name__, msg=message))
|
||||
19
model/my_model.py
Normal file
19
model/my_model.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import math
|
||||
import random
|
||||
from model.base_model import Model
|
||||
|
||||
class MyModel(Model):
|
||||
def __init__(self, coords):
|
||||
super().__init__(coords)
|
||||
"""
|
||||
Put your initialization here.
|
||||
"""
|
||||
self.log("Nothing to initialize in your model now")
|
||||
|
||||
def fit(self, max_it=1000, visualize=False):
|
||||
"""
|
||||
Put your iteration process here.
|
||||
"""
|
||||
self.log("Nothing happens in your model now")
|
||||
|
||||
return self.best_solution, self.fitness_list
|
||||
Reference in New Issue
Block a user