Lecture 32: Tabu Search#
Overview#
Unlike the Hill Climb algorithm, the Tabu Search algorithm navigates the solution space while avoiding local optima traps. To do so, the algorithm utilises a tabu list –
Note, unlike the exclusively exploitative Hill Climb algorithm, the Tabu Search algorithm balances exploration and exploitation of the solution landscape. It does so by keeping a track of recently visited solutions to avoid, thus forcing domain space exploration (diversification), while storing desirable solutions to recycle, thus enabling exploitation (intensification). Nonetheless, the exploration-exploitation balance is contingent on input parameters, namely, number of candidate solutions
Nonetheless, due to the ability of the Tabu Search alogirhm to effectively escape local optima traps, it has been extensively deployed for optimisation in Transportation Engineering, especially in routing-based problems such as the vehicle routing problem or the dial-a-ride problem.
Pseudo Code#
Procedure
// initialise current solution as the initial solution // initialise best solution as the current solution // initialise tabu list // initialise acceptance listwhile
do // repeat until converged // generate random candidates from neighborhood of the current solution // set new solution to the best feasible candidate solution // update the current solution to the new solutionif
then // if the current solution is better than the best solution // update the best solution to the current solutionend if
// add the current solution to the tabu listif
then // if size of the tabu list is greater than the maximum size // remove the oldest solution from the tabu listend if
if
then // if the current solution satisfies acceptance criterion // add the solution to the acceptance listend if
end while
return
// return the best solution
Implementation#
import random
import numpy as np
import matplotlib.pyplot as plt
def ts(s_o, N, k, m, p, n=100, t=1e-5):
"""
Tabu Search Algorithm
Parameters:
- s_o: Initial solution
- N: Neighborhood function
- k: Number of candidate neighbors per iteration
- m: Maximum size of tabu list
- p: Acceptance criterion percentage
- n: number of iterations
- t: convergence threshold
Returns:
- Best solution in each iteration
"""
s = s_o # Current solution
s_b = s # Best solution found
S = [s_b] # Track best solutions over iterations
S_t = [] # Tabu list
S_a = [] # Acceptance list
i = 1
e = float('inf')
converged = False
while not converged:
S_n = [N(s) for _ in range(k)] # Generate k random neighbors
S_n = [s_n for s_n in S_n if s_n not in S_t + S_a] # Filter tabu and accepted solutions
s_n = S_n[np.argmin([f(s_n) for s_n in S_n])] # Select best neighbour
s = s_n # Update current solution
if f(s) < f(s_b): # If better than best found so far, update
e = f(s_b) - f(s)
s_b = s
S_t.append(s) # Add current solution to tabu list
if len(S_t) > m: # Maintain tabu list size
S_t.pop(0)
if f(s) < (1 + p) * f(s_b): # Acceptance condition
S_a.append(s)
S.append(s_b) # Store the best solution at each iteration
i += 1
if i >= n or e <= t: # Convergence condition
converged = True
return S
Case Study#
Ackley Function#
def f(s):
"""
Computes the Ackley function for given solution s.
Parameters:
- s: Tuple of input coordinates
Returns:
- Function value at s
"""
x, y = s
return -20 * np.exp(-0.2 * np.sqrt((x**2 + y**2) / 2)) + -np.exp((np.cos(2 * np.pi * x) + np.cos(2 * np.pi * y)) / 2) + 20 + np.exp(1)
# Generate a grid of (x, y) values
X, Y = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100))
Z = np.array([[f(s) for s in zip(r, c)] for r, c in zip(X, Y)])
# Plot the Ackley function
fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis')
# Labels and title
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Ackley Function Value")
ax.set_title("Ackley Function Surface Plot")
Text(0.5, 0.92, 'Ackley Function Surface Plot')

def N(s):
x, y = s
x += np.random.uniform(-0.5, 0.5)
y += np.random.uniform(-0.5, 0.5)
s = [x,y]
return s
s_o = [random.uniform(-5, 5), random.uniform(-5, 5)]
S = ts(s_o, N, 10, 5, 0.1)
s_b = S[-1]
F = [f(s) for s in S]
# Report outcome
print("Best solution:", s_b)
print("Objective function value:", f(s_b))
# Convergence plot
fig = plt.figure()
plt.plot(F)
plt.xlabel("Iteration")
plt.ylabel("Objective Function Value")
plt.title("Convergence of Tabu Search Algorithm")
plt.grid()
plt.show()
Best solution: [-0.007649750126353383, -0.009982769122940938]
Objective function value: 0.03977984488350872

Traveling Salesman Problem#
The Traveling Salesman Problem (TSP) is one of the most fundamental and extensively studied combinatorial optimization problems in operations research, wherein, a salesman needs to visit N cities exactly once and return to the starting city while minimizing the total travel distance. Here we have the att48 dataset, which is a well-known TSP benchmark consistsing of 48 US cities.
City |
X |
Y |
---|---|---|
1 |
6734 |
1453 |
2 |
2233 |
10 |
3 |
5530 |
1424 |
4 |
401 |
841 |
5 |
3082 |
1644 |
6 |
7608 |
4458 |
7 |
7573 |
3716 |
8 |
7265 |
1268 |
9 |
6898 |
1885 |
10 |
1112 |
2049 |
11 |
5468 |
2606 |
12 |
5989 |
2873 |
13 |
4706 |
2674 |
14 |
4612 |
2035 |
15 |
6347 |
2683 |
16 |
6107 |
669 |
17 |
7611 |
5184 |
18 |
7462 |
3590 |
19 |
7732 |
4723 |
20 |
5900 |
3561 |
21 |
4483 |
3369 |
22 |
6101 |
1110 |
23 |
5199 |
2182 |
24 |
1633 |
2809 |
25 |
4307 |
2322 |
26 |
675 |
1006 |
27 |
7555 |
4819 |
28 |
7541 |
3981 |
29 |
3177 |
756 |
30 |
7352 |
4506 |
31 |
7545 |
2801 |
32 |
3245 |
3305 |
33 |
6426 |
3173 |
34 |
4608 |
1198 |
35 |
23 |
2216 |
36 |
7248 |
3779 |
37 |
7762 |
4595 |
38 |
7392 |
2244 |
39 |
3484 |
2829 |
40 |
6271 |
2135 |
41 |
4985 |
140 |
42 |
1916 |
1569 |
43 |
7280 |
4899 |
44 |
7509 |
3239 |
45 |
10 |
2676 |
46 |
6807 |
2993 |
47 |
5185 |
3258 |
48 |
3023 |
1942 |
import random
import matplotlib.pyplot as plt
# Nodes
C = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]
X = [6734, 2233, 5530, 401, 3082, 7608, 7573, 7265, 6898, 1112, 5468, 5989, 4706, 4612, 6347, 6107, 7611, 7462, 7732, 5900, 4483, 6101, 5199, 1633, 4307, 675, 7555, 7541, 3177, 7352, 7545, 3245, 6426, 4608, 23, 7248, 7762, 7392, 3484, 6271, 4985, 1916, 7280, 7509, 10, 6807, 5185, 3023]
Y = [1453, 10, 1424, 841, 1644, 4458, 3716, 1268, 1885, 2049, 2606, 2873, 2674, 2035, 2683, 669, 5184, 3590, 4723, 3561, 3369, 1110, 2182, 2809, 2322, 1006, 4819, 3981, 756, 4506, 2801, 3305, 3173, 1198, 2216, 3779, 4595, 2244, 2829, 2135, 140, 1569, 4899, 3239, 2676, 2993, 3258, 1942]
# Arcs
A = np.zeros((48, 48))
for i in range(48):
for j in range(48):
A[i][j] = np.sqrt((X[i] - X[j])**2 + (Y[i] - Y[j])**2)
# Compute total route cost
def f(s):
c = A[s[-1]][s[0]]
for i in range(len(s)-1):
c += A[s[i]][s[i+1]]
return c
# Generate 2-opt neighborhood
def N(s):
s = s.copy()
i, j = sorted(random.sample(range(len(C)), 2))
s = s[:i] + s[i:j+1][::-1] + s[j+1:]
return s
s_o = random.sample(C, len(C))
S = ts(s_o, N, 100, 75, 0.1, 1000, 1e-5)
s_b = S[-1]
F = [f(s) for s in S]
s = s_b
x = [X[c] for c in s]
y = [Y[c] for c in s]
x.append(X[s[0]])
y.append(Y[s[0]])
# Report outcome
print("Best solution:", s_b)
print("Objective function value:", f(s_b))
# TSP
plt.figure()
plt.plot(x, y, 'o-', markersize=8, label="TSP Route")
plt.title("Tabu Search Solution for att48 TSP")
plt.xlabel("X Coordinate")
plt.ylabel("Y Coordinate")
plt.legend()
plt.grid()
plt.show()
# Convergence plot
fig = plt.figure()
plt.plot(F)
plt.xlabel("Iteration")
plt.ylabel("Objective Function Value")
plt.title("Convergence of Tabu Search Algorithm")
plt.grid()
plt.show()
[41, 9, 29, 38, 16, 0, 33, 12, 31, 4, 15, 13, 36, 8, 35, 11, 32, 39, 3, 17, 14, 25, 24, 1, 37, 19, 44, 20, 21, 34, 30, 27, 2, 26, 5, 46, 7, 43, 28, 18, 22, 40, 47, 10, 23, 45, 42, 6]
Best solution: [20, 46, 19, 32, 45, 17, 35, 29, 5, 26, 42, 16, 18, 36, 27, 6, 43, 30, 37, 7, 0, 8, 39, 14, 11, 10, 22, 2, 21, 15, 40, 33, 47, 4, 28, 1, 41, 25, 3, 34, 44, 9, 23, 31, 38, 24, 13, 12]
Objective function value: 34336.43784400215

