Implement simulated annealing
This commit is contained in:
parent
89045a04c8
commit
baf6e40e15
|
@ -0,0 +1,111 @@
|
|||
from numpy.random import choice, randint, random
|
||||
from pandas import DataFrame
|
||||
from itertools import combinations
|
||||
from math import exp, log
|
||||
|
||||
|
||||
def get_row_distance(source, destination, data):
|
||||
row = data.query(
|
||||
"""(source == @source and destination == @destination) or \
|
||||
(source == @destination and destination == @source)"""
|
||||
)
|
||||
return row["distance"].values[0]
|
||||
|
||||
|
||||
def compute_distance(element, solution, data):
|
||||
accumulator = 0
|
||||
distinct_elements = solution.query(f"point != {element}")
|
||||
for _, item in distinct_elements.iterrows():
|
||||
accumulator += get_row_distance(
|
||||
source=element, destination=item.point, data=data
|
||||
)
|
||||
return accumulator
|
||||
|
||||
|
||||
def generate_first_solution(n, m, data):
|
||||
solution = DataFrame(columns=["point", "distance", "fitness"])
|
||||
solution["point"] = choice(n, size=m, replace=False)
|
||||
solution["distance"] = solution["point"].apply(
|
||||
func=compute_distance, solution=solution, data=data
|
||||
)
|
||||
solution = evaluate_solution(solution, data)
|
||||
return solution
|
||||
|
||||
|
||||
def evaluate_solution(solution, data):
|
||||
fitness = 0
|
||||
comb = combinations(solution.index, r=2)
|
||||
for index in list(comb):
|
||||
elements = solution.loc[index, :]
|
||||
fitness += get_row_distance(
|
||||
source=elements["point"].head(n=1).values[0],
|
||||
destination=elements["point"].tail(n=1).values[0],
|
||||
data=data,
|
||||
)
|
||||
solution["fitness"] = fitness
|
||||
return solution
|
||||
|
||||
|
||||
def element_in_dataframe(solution, element):
|
||||
duplicates = solution.query(f"point == {element}")
|
||||
return not duplicates.empty
|
||||
|
||||
|
||||
def generate_neighbour(previous, n, data):
|
||||
solution = previous.copy()
|
||||
random_index = randint(len(solution.point.values))
|
||||
random_element = randint(n)
|
||||
while element_in_dataframe(solution=solution, element=random_element):
|
||||
random_element = randint(n)
|
||||
solution["point"].loc[random_index] = random_element
|
||||
solution["distance"].loc[random_index] = compute_distance(
|
||||
element=solution["point"].loc[random_index], solution=solution, data=data
|
||||
)
|
||||
solution = evaluate_solution(solution, data)
|
||||
return solution
|
||||
|
||||
|
||||
def get_temperatures(initial_fitness, mu=0.3, phi=0.3):
|
||||
T0 = (mu * initial_fitness) / -(log(phi))
|
||||
Tf = 1e-3
|
||||
return T0, Tf
|
||||
|
||||
|
||||
def get_metaparameters(n, max_iterations):
|
||||
max_neighbours = 10 * n
|
||||
max_successes = 0.1 * max_neighbours
|
||||
M = max_iterations / max_neighbours
|
||||
return max_neighbours, max_successes, M
|
||||
|
||||
|
||||
def beta(T0, Tf, M):
|
||||
return (T0 - Tf) / (M * T0 * Tf)
|
||||
|
||||
|
||||
def cool_down(T, T0, Tf, M):
|
||||
return T / (1 + beta(T0, Tf, M) * T)
|
||||
|
||||
|
||||
def simulated_annealing(n, m, data, max_iterations=10000):
|
||||
initial_solution = generate_first_solution(n, m, data)
|
||||
T0, Tf = get_temperatures(initial_fitness=initial_solution.fitness.values[0])
|
||||
max_neighbours, max_successes, M = get_metaparameters(n, max_iterations)
|
||||
current_solution = initial_solution
|
||||
best_solution = initial_solution
|
||||
T = T0
|
||||
while T > T0:
|
||||
num_successes = 0
|
||||
for _ in range(max_neighbours):
|
||||
neighbour = generate_neighbour(current_solution, n, data)
|
||||
delta = neighbour.fitness.values[0] - current_solution.fitness.values[0]
|
||||
if delta > 0 or random() < exp((-delta) / T):
|
||||
current_sol = neighbour
|
||||
num_successes += 1
|
||||
if current_solution.fitness.values[0] > best_solution.fitness.values[0]:
|
||||
best_solution = current_sol
|
||||
if num_successes >= max_successes:
|
||||
break
|
||||
if num_successes == 0:
|
||||
break
|
||||
T = cool_down(T, T0, Tf, M)
|
||||
return best_solution
|
Loading…
Reference in New Issue