Skip to content

Commit c75b95a

Browse files
committed
Added iterated local search. Needs more testing though (see number trace).
1 parent f1f7683 commit c75b95a

File tree

1 file changed

+147
-0
lines changed

1 file changed

+147
-0
lines changed
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
#! usr/bin/env python3
2+
3+
import math
4+
5+
import random
6+
7+
"""
8+
2.5
9+
10+
Iterated Local Search improves upon Multi-Restart Search by
11+
sampling in the broader neighborhood of candidate solutions
12+
and using a Local Search technique to refine solutions to
13+
their local optima. Iterated Local Search explores a sequence
14+
of solutions created as perturbations of the current best
15+
solution, the result of which is refined using an embedded
16+
heuristic.
17+
18+
The code listing applies the algorithm to the Berlin52 instance
19+
of the Traveling Salesman Problem, taken from TSPLIB. The optimal
20+
tour distance for the Berlin52 instance is 7542 units.
21+
22+
@author Chad Estioco
23+
"""
24+
25+
def euc_2d(c1, c2):
26+
return math.sqrt(((c1[0] - c2[0]) ** 2) + ((c1[1] - c2[1]) ** 2))
27+
28+
def cost(permutation, cities):
29+
distance = 0
30+
limit = len(permutation)
31+
32+
for i in range(limit):
33+
if i == (limit - 1):
34+
c2 = permutation[0]
35+
else:
36+
c2 = permutation[i + 1]
37+
38+
distance += euc_2d(cities[permutation[i]], cities[c2])
39+
40+
return distance
41+
42+
def random_permutation(cities):
43+
perm = [i for i in range(len(cities))]
44+
45+
for i in perm:
46+
r = random.randint(0, len(perm) - 1 - i) + i
47+
perm[r], perm[i] = perm[i], perm[r]
48+
49+
return perm
50+
51+
def stochastic_two_opt(permutation):
52+
perm = [permutation[i] for i in range(len(permutation))]
53+
upper_bound = len(perm) - 1
54+
c1, c2 = random.randint(0, upper_bound), random.randint(0, upper_bound)
55+
exclude = [c1]
56+
57+
if c1 == 0:
58+
exclude.append(upper_bound)
59+
else:
60+
exclude.append(c1 - 1)
61+
62+
if c1 == upper_bound:
63+
exclude.append(0)
64+
else:
65+
exclude.append(c1 + 1)
66+
67+
while c2 in exclude:
68+
c2 = random.randint(0, upper_bound)
69+
70+
if c2 < c1:
71+
c1, c2 = c2, c1
72+
73+
perm_range = perm[c1:c2]
74+
perm_range.reverse()
75+
perm[c1:c2] = perm_range
76+
77+
return perm
78+
79+
#FIXME modifying and returning an argument?
80+
def local_search(best, cities, max_no_improv):
81+
count = 0
82+
83+
while count < max_no_improv:
84+
candidate = {}
85+
candidate["vector"] = stochastic_two_opt(best["vector"])
86+
candidate["cost"] = cost(candidate["vector"], cities)
87+
88+
if candidate["cost"] < best["cost"]:
89+
count = 0
90+
else:
91+
count += 1
92+
93+
if candidate["cost"] < best["cost"]:
94+
best = candidate
95+
96+
return best
97+
98+
def double_bridge_move(perm):
99+
pos1 = 1 + random.randint(0, math.floor(len(perm) / 4))
100+
pos2 = pos1 + 1 + random.randint(0, math.floor(len(perm) / 4))
101+
pos3 = pos2 + 1 + random.randint(0, math.floor(len(perm) / 4))
102+
p1 = perm[0:pos1] + perm[pos3:len(perm)]
103+
p2 = perm[pos2:pos3] + perm[pos1:pos2]
104+
return p1 + p2
105+
106+
def perturbation(cities, best):
107+
candidate = {}
108+
candidate["vector"] = double_bridge_move(best["vector"])
109+
candidate["cost"] = cost(candidate["vector"], cities)
110+
return candidate
111+
112+
def search(cities, max_iterations, max_no_improv):
113+
best = {}
114+
best["vector"] = random_permutation(cities)
115+
best["cost"] = cost(best["vector"], cities)
116+
best = local_search(best, cities, max_no_improv)
117+
118+
for i in range(max_iterations):
119+
candidate = perturbation(cities, best)
120+
candidate = local_search(candidate, cities, max_no_improv)
121+
122+
if candidate["cost"] < best["cost"]:
123+
best = candidate
124+
125+
print("Iteration #" + str(i) + " best = " + str(best["cost"]))
126+
127+
return best
128+
129+
if __name__ == "__main__":
130+
# Problem configuration
131+
berlin52 = [[565,575],[25,185],[345,750],[945,685],[845,655],
132+
[880,660],[25,230],[525,1000],[580,1175],[650,1130],[1605,620],
133+
[1220,580],[1465,200],[1530,5],[845,680],[725,370],[145,665],
134+
[415,635],[510,875],[560,365],[300,465],[520,585],[480,415],
135+
[835,625],[975,580],[1215,245],[1320,315],[1250,400],[660,180],
136+
[410,250],[420,555],[575,665],[1150,1160],[700,580],[685,595],
137+
[685,610],[770,610],[795,645],[720,635],[760,650],[475,960],
138+
[95,260],[875,920],[700,500],[555,815],[830,485],[1170,65],
139+
[830,610],[605,625],[595,360],[1340,725],[1740,245]]
140+
141+
# Algorithm configuration
142+
max_iterations = 100
143+
max_no_improv = 50
144+
145+
# Execute the algorithm
146+
best = search(berlin52, max_iterations, max_no_improv)
147+
print("Done. Best solution: c = " + str(best["cost"]) +", v = " + str(best["vector"]))

0 commit comments

Comments
 (0)