|
| 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