Local Search and Simulated Annealing › Formula
Machine Learning from ScratchAll chapters Local Search and Simulated Annealing › FormulaSection view · Sections in this chapter4 / 10 ↓
Reading tools Definitions, links & notes
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
There are four objects to define, and they fit together in a natural order:
- A representation says what a candidate solution is.
- An objective assigns a number to each candidate.
- A neighborhood says which small edits produce the candidates the search is allowed to see next.
- An acceptance rule decides whether to move to a proposed neighbor.
The order matters. The representation fixes the universe of possible answers; the objective ranks them; the neighborhood defines the local graph the search can traverse; and the acceptance rule determines whether the walk is greedy or stochastic. Each choice below is made with the same routing instance in mind, so that the formal definitions can be checked immediately against concrete numbers.
Solution representations and feasible neighborhoods
We encode a solution as a permutation of the customer identifiers . The decoder splits the permutation into two routes of four customers each, because the total demand is eight and each of the two vehicles has capacity four:
where node is the depot. Because each vehicle carries exactly four customers, every permutation of the eight customers automatically produces two capacity-feasible routes, so capacity alone never rules out a permutation. Time-window and depot-return feasibility are checked separately, and on this instance they are deliberately loose. Each customer has demand one, service takes one time unit, and all customer and depot windows are . Travel time equals Euclidean distance in the chosen units. The coordinates fit in an 8-by-6 rectangle, so every arc is at most 10 units. Each four-customer route uses five arcs and four service periods, giving a return-time bound of . With window openings at zero, no waiting is needed; every valid permutation therefore satisfies these capacity and time limits. The loose windows isolate search behavior; this example does not test restrictive time-window handling.
The objective sums the Euclidean arc lengths over both closed tours:
where:
- : a permutation of the eight customer identifiers
- : the customer visited at position in the permutation
- : the Euclidean distance between two coordinates
- the first line traverses the first route's internal arcs (positions to ), and the two depot arcs and close that tour
- the second line traverses the second route's internal arcs (positions to ), and the two depot arcs and close that tour
There is no lateness penalty and no service-time term in the objective; service times only constrain the schedule validation.
A representation by itself is not enough: the search needs a notion of "nearby." That is the neighborhood. Here the neighborhood is the set of all inclusive segment reversals. A move selects indices and replaces the segment with its reverse. This move set is not arbitrary. A within-route reversal preserves the undirected internal-edge lengths and rewires two boundary arc positions. Reversing an entire route can leave its aggregate undirected edge multiset unchanged. A reversal crossing the split changes route assignments and route-labelled depot attachments, even when the aggregate depot-edge multiset is unchanged, so it must be scored through the full decoder here. When the reversed segment crosses the position- boundary, the move changes how customers are assigned to vehicles. Thus the neighborhood can reassign customers between routes.
This neighborhood has two properties we rely on. First, adjacent reversals (with ) are simple swaps, and swaps alone connect the entire permutation space; the wider reversals only add shortcuts. Consequently the state graph is connected and every permutation is reachable from every other permutation. Second, the wider reversals are geometrically meaningful: reversing a contiguous piece of a route is the 2-opt move, which can uncross edges and shorten a tour. A neighborhood that only reversed within each vehicle would be disconnected across the route boundary and would forbid exactly the cross-split moves a routing search needs.
PythonIn [3]Expand
import numpy as np
COORDS = np.array(
[[0, 0], [1, 3], [2, 6], [4, 5], [7, 6], [8, 3], [6, 1], [3, 1], [5, 3]],
dtype=float,
)
DEMANDS = np.array([0, 1, 1, 1, 1, 1, 1, 1, 1])
CAPACITY = 4
WINDOWS = [(0.0, 80.0)] * 9
SERVICE = np.array([0.0, 1, 1, 1, 1, 1, 1, 1, 1])
diff = COORDS[:, None, :] - COORDS[None, :, :]
D = np.sqrt((diff**2).sum(axis=-1))PythonIn [4]Expand
def tour_distance(perm):
total = 0.0
for route in (perm[:4], perm[4:]):
prev = 0
for c in route:
total += D[prev, c]
prev = c
total += D[prev, 0]
return total
def is_feasible(perm):
values = np.asarray(perm)
if (
values.shape != (8,)
or not np.issubdtype(values.dtype, np.integer)
or set(values.tolist()) != set(range(1, 9))
):
return False
for route in (perm[:4], perm[4:]):
load = 0
t = 0.0
prev = 0
for c in route:
load += DEMANDS[c]
if load > CAPACITY:
return False
arrival = t + D[prev, c]
start = max(arrival, WINDOWS[c][0])
if start > WINDOWS[c][1]:
return False
t = start + SERVICE[c]
prev = c
if t + D[prev, 0] > WINDOWS[0][1]:
return False
return True
def objective(perm):
return tour_distance(perm) if is_feasible(perm) else np.inf
def nearest_neighbor():
remaining = list(range(1, 9))
perm = []
for _ in range(2):
prev = 0
for _ in range(4):
best_c = min(remaining, key=lambda c: (D[prev, c], c))
perm.append(best_c)
remaining.remove(best_c)
prev = best_c
return tuple(perm)PythonIn [5]Expand
nn_perm = nearest_neighbor()
nn_length = objective(nn_perm)ConsoleOut [6]Expand
Nearest-neighbor permutation: (1, 7, 8, 3, 6, 5, 4, 2) Nearest-neighbor tour length: 40.856347 Nearest-neighbor permutation distance summary: mean arc = 4.085635
The nearest-neighbor heuristic, run through the decoder and objective above, returns a warm-start tour of length described by the printed output above. Because the customer count is only eight, we can enumerate every permutation and obtain the true minimum, which we will use as an exact reference later.
PythonIn [7]Expand
from itertools import permutations
best_exact, best_exact_perm = np.inf, None
for p in permutations(range(1, 9)):
d = objective(p)
if d < best_exact - 1e-15:
best_exact, best_exact_perm = d, pConsoleOut [8]Expand
Exact optimum: 37.35597706914 Achieved by: (6, 5, 4, 8, 1, 2, 3, 7) Nearest neighbor gap: 3.500370
Enumerating all permutations confirms the optimum is printed above, attained by the permutation printed above. This exhaustive reference search is outside the three budgeted heuristic runs, and we exclude it from every method's evaluation budget.
Hill climbing, local optima, and restarts
With the representation and neighborhood fixed, we can define an ideal steepest-descent rule: from the current permutation, choose a minimum-score neighbor and step only if it improves enough. The ideal strict-improvement rule is
where:
- : the current permutation at iteration
- : the set of all segment reversals of
- : the objective (total tour length) defined above
- : a small tolerance (here ) to guard against floating-point noise. The tolerance is not decoration: two mathematically equal tour lengths may differ in their last bits, and without a margin round-off can cause unproductive moves between tours that are equal for all practical purposes. Deterministic strict descent still cannot cycle, because every accepted step lowers the fixed computed score. If no neighbor improves by more than , the step is undefined: no improvement exceeding that tolerance is available in . This is a tolerance-based stopping point, which need not be an exact mathematical local minimum.
The code below uses an ordered, tolerance-aware scan rather than an exact argmin. The first scored neighbor becomes the scan candidate; a later neighbor replaces it only when its score is lower by more than . After a completed scan, the candidate is installed only if it improves the current score by more than . Differences within retain the earlier neighbor. The chosen candidate can therefore differ from the exact minimum; the final guard can even reject a scan when a later neighbor would have passed that guard. A budget-truncated scan returns the global best already scored, without installing a local step.
A local optimum is not a global one: there may exist a with that is several reversals away. Restarts handle local stopping by abandoning the current epoch when the tolerance-aware scan refuses to step and beginning a fresh epoch from a new permutation. Within an epoch, every accepted step strictly lowers the current computed score. The scan stopping rule is not an exact local-optimality certificate. The restart initialization is usually a fresh random permutation, and the overall best seen across all epochs is what we report.
Two subtleties matter because they change reported comparisons. First, when we scan the full neighborhood to select a neighbor, every neighbor evaluation consumes budget, including the neighbors we reject and including equal-cost duplicates. When the budget runs out mid-scan, the honest thing to do is return the best permutation already scored, not to spend one extra evaluation to finish the scan. Second, the fixed budget must include the evaluations spent starting the epochs, not only the improvement steps. If restart-initialization evaluations are excluded, the reported count understates the true scoring cost and can make the method appear more efficient than it is.
Annealing acceptance and temperature schedules
Simulated annealing uses the same representation, objective, and neighborhood. It proposes one random reversal instead of scanning for the best neighbor, then applies a stochastic acceptance rule. From the current state with objective , we propose a neighbor by sampling uniformly from the reversal neighborhood. We define the change in objective
and accept the proposal with probability
where:
- : the change in objective,
- : the temperature after evaluations (a positive scale that controls how freely uphill moves are accepted)
An improving move is always accepted; a worsening move is accepted with probability that falls as shrinks. When is large the search behaves like a random walk that pays little attention to the objective; as approaches zero, the probability of accepting any fixed positive increase approaches zero. Equal-cost moves still have acceptance probability one, so this limit differs from strict-improvement hill climbing.
The exponential acceptance rule can be read directly as a family of curves: for a fixed worsening move, higher temperature means more exploration, while lower temperature rapidly suppresses uphill moves.
PythonIn [9]Expand
T_GRID = np.geomspace(0.01, 5.0, 240)
DELTAS_TO_PLOT = np.array([0.1, 0.5, 1.0, 3.0])
ACCEPT_PROBS = np.exp(-DELTAS_TO_PLOT[:, None] / T_GRID[None, :])VisualizationOut [10]Expand

The acceptance rule is not an arbitrary hack. It is the Metropolis-Hastings rule for the Boltzmann (Gibbs) target
Here assigns probability to a permutation at a fixed . The proportionality sign means dividing by , with ranging over the finite permutation space. Every objective is finite on this deliberately feasible instance, so this normalizing sum is positive and finite.
To see why, note that if we propose symmetrically from (so ), then the ratio
and the Metropolis rule accepts with probability , which is exactly the formula above. This is why sampling two distinct indices, sorting them, and reversing the inclusive segment yields a symmetric proposal over all reversals: any reversal is as likely as its reverse.
The temperature must fall over time according to a cooling schedule. Here we choose geometric cooling:
where:
- : the initial temperature (the hot start)
- : the final temperature (near-greedy for worsening moves large relative to ; ties and sufficiently small increases can still be accepted)
- : the total evaluation budget
- : the evaluation index, running from to
For and , this schedule has endpoints and . Index zero is the initial nearest-neighbor score, where there is no acceptance test; the first proposal uses index one. Classical finite-state convergence results use schedules such as with a sufficiently large constant relative to the critical escape depth and suitable proposal conditions. Our finite geometric schedule has no certificate of global optimality by itself.
The schedule above has a simple shape that we can inspect before running the algorithm: most of the cooling happens early, and the final evaluations run at a near-greedy temperature.
PythonIn [11]Expand
K_EVALS = 300
T0 = 2.0
T1 = 0.01
EVAL_INDEX = np.arange(K_EVALS)
COOLING_TEMPS = T0 * (T1 / T0) ** (EVAL_INDEX / (K_EVALS - 1))VisualizationOut [12]Expand

The nearest-neighbor tour supplies the initial state. The initial temperature controls acceptance of worsening perturbations; it does not bound how far the current tour can deteriorate over several accepted moves. The separate incumbent retains the best tour already scored. A final temperature near zero favors polishing for worsening moves large relative to . The temperature scale must be checked against actual objective changes; these two endpoint values are choices for this experiment.
Mathematical Properties
Several properties are worth recording so we do not overclaim what these algorithms achieve. First, the permutation graph under reversals is connected: any permutation can be transformed into any other by a finite sequence of inclusive segment reversals, because swaps (adjacent reversals) alone generate all permutations. This guarantees that with a sufficiently long search simulated annealing can in principle visit any solution, which a disconnected neighborhood would not allow.
Second, for a fixed temperature , the Metropolis chain with symmetric proposals has as its stationary distribution. That gives simulated annealing a probabilistic meaning at a frozen temperature, where sufficiently equilibrated samples can approximate the Boltzmann distribution, but stationarity alone does not establish that a changing-temperature chain tracks those distributions. Decreasing temperature changes the transition rule and target distribution over time.
Third, under Hajek's conditions, a logarithmic cooling schedule that decreases slowly enough relative to the critical depth of the deepest nonglobal minimum makes the probability of being in the global-optimum set tend to one. This is an asymptotic statement under its hypotheses. It does not supply a finite-budget success probability for the schedule used here. In practice we measure quality empirically, across many seeds, and report the spread.
Finally, hill climbing with strict improvement terminates in finitely many steps: the objective strictly decreases by at least at every accepted step, and the solution space is finite, so the loop cannot run forever. Hypothetically, independent uniform restart states followed by the same deterministic complete descent produce identically distributed completed-epoch outcomes. Our first epoch has a deterministic nearest-neighbor start, and a fixed budget can truncate the last epoch, so those outcomes do not all share that distribution. The best objective observed cannot increase as more candidates are scored.
The formulas now define a complete search process: a permutation is a solution, the objective ranks it, the reversal neighborhood supplies local moves, hill climbing accepts only strict improvements, and annealing accepts improvements always but worsening moves with a temperature-controlled probability. The next section makes the neighborhood visible, so we can see why these rules behave differently on the same eight-customer instance.
Key Parameters
The three algorithms have small parameter surfaces. Hill climbing has just two: the budget and the seed (and an implicit restart rule). Simulated annealing adds the initial and final temperatures and , which set the geometric cooling curve. Random search adds nothing beyond a budget and a seed.
In our runs we hold the budget fixed at evaluations and run all three across the same seeds. Both hill climbing and annealing start from the nearest-neighbor solution, which costs one evaluation immediately. That common warm start controls initialization: different starts would confound the effect of the search with the choice of starting point.
For annealing, we choose and . The initial temperature is chosen so that a typical worsening move is accepted with probability early, but a worsening move (an illustrative increase in these distance units) is only accepted with probability . At a final temperature of , a increase has extremely small acceptance probability, although sufficiently small worsening moves can still be accepted. Finite positive temperature is not a strict-improvement rule.
Comments
Add to the conversation
A question, a correction, or an example from your own work.
No comments yet. Be the first to share your thoughts!