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