Skip to content

Commit a496a7e

Browse files
committed
Added guided local search. Needs verification.
1 parent e43c80c commit a496a7e

File tree

1 file changed

+199
-0
lines changed

1 file changed

+199
-0
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#! usr/bin/env python3
2+
3+
import math
4+
5+
import random
6+
7+
"""
8+
2.6
9+
10+
Guided Local Search uses penalties to encourage a Local Search
11+
technique to escape local optima and discover the global optima.
12+
A Local Search algorithm is run until it gets stuck in a local
13+
optima. The features from the local optima are evaluated and
14+
penalized, the results of which are used in an augmented cost
15+
function employed by the Local Search procedure. The Local Search
16+
is repeated a number of times using the last local optima discovered
17+
and the augmented cost function that guides exploration away from
18+
solutions with featured present in the discovered local optima.
19+
20+
This implementation applies Guided Local Search to the Berlin52
21+
instance of TSP from TSPLIB.
22+
"""
23+
24+
def euc_2d(c1, c2):
25+
"""
26+
Same as the one in 2.5
27+
"""
28+
return math.sqrt(((c1[0] - c2[0]) ** 2) + ((c1[1] - c2[1]) ** 2))
29+
30+
def random_permutation(cities):
31+
"""
32+
Same as the one in 2.5
33+
"""
34+
perm = [i for i in range(len(cities))]
35+
36+
for i in perm:
37+
r = random.randint(0, len(perm) - 1 - i) + i
38+
perm[r], perm[i] = perm[i], perm[r]
39+
40+
return perm
41+
42+
def stochastic_two_opt(permutation):
43+
"""
44+
Same as the one in 2.5
45+
"""
46+
perm = [permutation[i] for i in range(len(permutation))]
47+
upper_bound = len(perm) - 1
48+
c1, c2 = random.randint(0, upper_bound), random.randint(0, upper_bound)
49+
exclude = [c1]
50+
51+
if c1 == 0:
52+
exclude.append(upper_bound)
53+
else:
54+
exclude.append(c1 - 1)
55+
56+
if c1 == upper_bound:
57+
exclude.append(0)
58+
else:
59+
exclude.append(c1 + 1)
60+
61+
while c2 in exclude:
62+
c2 = random.randint(0, upper_bound)
63+
64+
if c2 < c1:
65+
c1, c2 = c2, c1
66+
67+
perm_range = perm[c1:c2]
68+
perm_range.reverse()
69+
perm[c1:c2] = perm_range
70+
71+
return perm
72+
73+
def augmented_cost(permutation, penalties, cities, l):
74+
distance, augmented = 0, 0
75+
limit = len(permutation)
76+
77+
for i in range(limit):
78+
c1 = permutation[i]
79+
80+
if i == (limit - 1):
81+
c2 = permutation[0]
82+
else:
83+
c2 = permutation[i + 1]
84+
85+
if c2 < c1:
86+
c1, c2 = c2, c1
87+
88+
d = euc_2d(cities[c1], cities[c2])
89+
distance += d
90+
augmented += d + (l * penalties[c1][c2])
91+
92+
return [distance, augmented]
93+
94+
def cost(cand, penalties, cities, l):
95+
cost, acost = augmented_cost(cand["vector"], penalties, cities, l)
96+
cand["cost"], cand["aug_cost"] = cost, acost
97+
98+
def local_search(current, cities, penalties, max_no_improv, l):
99+
cost(current, penalties, cities, l)
100+
count = 0
101+
102+
# begin-until hack
103+
while True:
104+
candidate = {}
105+
candidate["vector"] = stochastic_two_opt(current["vector"])
106+
cost(candidate, penalties, cities, l)
107+
108+
if candidate["aug_cost"] < current["aug_cost"]:
109+
count = 0
110+
else:
111+
count += 1
112+
113+
if candidate["aug_cost"] < current["aug_cost"]:
114+
current = candidate
115+
116+
if count >= max_no_improv:
117+
return current
118+
119+
def calculate_feature_utilities(penal, cities, permutation):
120+
limit = len(permutation)
121+
limit_list = range(limit)
122+
utilities = [0 for i in limit_list]
123+
124+
for i in limit_list:
125+
c1 = permutation[i]
126+
127+
if i == (limit - 1):
128+
c2 = permutation[0]
129+
else:
130+
c2 = permutation[i + 1]
131+
132+
if c2 < c1:
133+
c1, c2 = c2, c1
134+
135+
utilities[i] = euc_2d(cities[c1], cities[c2]) / (1 + penal[c1][c2])
136+
137+
return utilities
138+
139+
def update_penalties(penalties, cities, permutation, utilities):
140+
max_util = max(utilities)
141+
limit = len(permutation)
142+
143+
for i in range(limit):
144+
c1 = permutation[i]
145+
146+
if i == (limit - 1):
147+
c2 = permutation[0]
148+
else:
149+
c2 = permutation[i + 1]
150+
151+
if c2 < c1:
152+
c1, c2 = c2, c1
153+
154+
if utilities[i] == max_util:
155+
penalties[c1][c2] += 1
156+
157+
return penalties
158+
159+
def search(max_iterations, cities, max_no_improv, l):
160+
current = {}
161+
current["vector"] = random_permutation(cities)
162+
best = None
163+
cities_count_list = range(len(cities))
164+
penalties = [[0 for i in cities_count_list] for j in cities_count_list]
165+
166+
for i in range(max_iterations):
167+
current = local_search(current, cities, penalties, max_no_improv, l)
168+
utilities = calculate_feature_utilities(penalties, cities, current["vector"])
169+
update_penalties(penalties, cities, current["vector"], utilities)
170+
171+
if best is None or current["cost"] < best["cost"]:
172+
best = current
173+
174+
print("Iteration #" + str(i + 1) + ", best = " + str(best["cost"]) + ", aug = " + str(best["aug_cost"]))
175+
176+
return best
177+
178+
if __name__ == "__main__":
179+
# Problem configuration
180+
berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
181+
[880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
182+
[1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
183+
[415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
184+
[835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
185+
[410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
186+
[685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
187+
[95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
188+
[830,610],[605,625],[595,360],[1340,725],[1740,245]]
189+
190+
# Algorithm configuration
191+
max_iterations = 150
192+
max_no_improv = 20
193+
alpha = 0.3
194+
local_search_optima = 12000
195+
l = alpha * (local_search_optima / len(berlin52))
196+
197+
# Execute the algorithm
198+
best = search(max_iterations, berlin52, max_no_improv, l)
199+
print("Done. Best solution: c = " + str(best["cost"]) + ", v = " + str(best["vector"]))

0 commit comments

Comments
 (0)