Why Simulated Annealing Matters
A local optimizer that accepts only improvements can become trapped behind a barrier. Imagine a configuration with energy $0$ whose immediate neighbor has energy $4$, but the next states lead to energy $1$ and then $-5$. Pure descent cannot take the first step. Simulated annealing sometimes accepts that temporary increase, allowing exploration beyond the current basin.
The algorithm minimizes an objective called energy. It samples a candidate move, always accepts a non-worsening candidate, and sometimes accepts a worse one according to a positive temperature. Early, warmer iterations can cross barriers; later, cooler iterations make energy increases less likely. A separate best-ever state preserves good discoveries while the current state continues to wander.
The implementation uses a small finite energy table, a symmetric bounded proposal, one portable seeded generator, geometric cooling, and explicit resource limits. Every proposal and decision is recorded. It is a reproducible optimization heuristic; reaching a temperature or iteration limit does not establish that the global minimum has been found.
Explore beyond a local minimum by accepting an energy increase $\Delta E\gt0$ with probability $\exp(-\Delta E/T)$ at positive temperature $T$, while always accepting non-worsening proposals. Gradually cool the search and preserve the lowest-energy state ever found, because the current state may later become worse.
Core Concepts and Terminology
Let $S$ be the feasible states, $E(x)$ the energy to minimize, and $q(y\mid x)$ the probability of proposing candidate $y$ from current state $x$. Define the energy difference by candidate minus current:
$$\Delta E=E(y)-E(x).$$
For a symmetric proposal, $q(y\mid x)=q(x\mid y)$, the Metropolis acceptance probability at $T\gt0$ is
$$a_T(x,y)=\min\left(1,\exp\left(-\frac{\Delta E}{T}\right)\right).$$
Thus $\Delta E\le0$ is accepted, including equal-energy moves and self-proposals. For $\Delta E\gt0$, draw $U$ uniformly from $(0,1)$ in the ideal model and accept when $U\lt\exp(-\Delta E/T)$. Higher barriers and lower temperatures both reduce this probability. Temperature has the same scale as energy differences; it is not a probability.
The example states are integer indices. Propose a left or right unit step with equal probability; an outward step at an endpoint becomes a self-proposal. This produces symmetric adjacent transition probabilities, including at boundaries. Sampling uniformly from only the valid neighbors would instead be asymmetric at an endpoint and would require different acceptance logic for the usual Boltzmann interpretation.
The current state follows accepted proposals and can have increasing energy. The incumbent is the best state discovered; its energy never increases. The first three accepted moves in the landscape below are $0\to1\to2\to3$, with energies $0\to4\to1\to-5$.
| Term | Example | Role |
|---|---|---|
| Energy $E(x)$ | $[0,4,1,-5,0,2]$ on six indices | Quantity to minimize; negative values are valid |
| Proposal $q(y\mid x)$ | Choose direction $-1$ or $+1$, then clamp to the domain | Determines exploration and whether simple Metropolis acceptance is appropriate |
| Energy change $\Delta E$ | First move has $4-0=4$ | Positive means worse for minimization |
| Temperature $T$ | $T=5$ initially | Controls acceptance of uphill energy moves |
| Uphill acceptance | $\exp(-4/5)\approx0.449329$ | Permits temporary worsening to cross a barrier |
| Cooling factor $\alpha$ | Multiply temperature by $0.85$ after each decision | Changes exploration over proposal time, including rejections |
| Current / best | Final current energy $1$; best energy $-5$ | Separates exploration state from the returned optimization result |
| Self-proposal | From $0$, a left step stays at $0$ | Preserves the bounded symmetric proposal and counts as an accepted non-change |
| Log-space decision | $\log U\lt-\Delta E/T$ | Avoids explicitly forming a potentially underflowing exponential |
| Stop reason | STEP_LIMIT, COOLED, EVAL_LIMIT, TEMP_STALLED | Reports why a finite run ended; none is a global-optimality certificate |
How Simulated Annealing Works, Step by Step
- Validate the model and schedule. Require a valid initial state, bounded integer energies, a positive finite starting temperature, a positive temperature floor at most the start, and finite cooling factor $0\lt\alpha\lt1$. Initialize one random generator for the whole run.
- Evaluate the initial state. Spend one objective call and initialize both current and best. If the global evaluation budget is zero, return
EVAL_LIMITwith no meaningful energy value. A zero proposal cap still allows this initial evaluation when budget permits. - Check stopping conditions before drawing a proposal. Stop first at the proposal cap, then if temperature is below the floor, then if floating-point cooling failed to change it, then if another evaluation would exceed budget. A blocked evaluation therefore consumes no proposal randomness.
- Propose and evaluate one state. Draw a direction, clamp the unit step to $[0,n-1]$, evaluate the candidate, and count the call even for a self-proposal. Compute $\Delta E$ in signed 64-bit arithmetic in C++ and Java.
- Make the acceptance decision. If $\Delta E\le0$, accept without an acceptance draw. Otherwise draw $U\in(0,1)$ and use the mathematically equivalent test
$$\log U\lt-\frac{\Delta E}{T}.$$
This avoids computing $\exp(-\Delta E/T)$, which can underflow to zero. If the quotient is too large to represent and becomes positive infinity before negation, the finite log draw cannot pass the resulting negative-infinity threshold. 6. Update current and best separately. On acceptance, replace current and its cached energy; update best only on a strict energy improvement. On rejection, current stays unchanged. Equal best energies keep the earliest best state. A rejected candidate is worse than current and therefore cannot beat the already no-worse incumbent. 7. Record the complete decision and cool. Save temperature used, old state/energy, direction, raw draws, candidate/energy, difference, acceptance, resulting current and best, and counters. Multiply temperature by $\alpha$ after every decision, including rejection and self-proposal. Record whether the new represented temperature equals the old one, then repeat.
With zero-based proposal index $k$, the ideal geometric schedule is $T_k=T_0\alpha^k$; proposal $1$ uses $T_0$. The programs multiply successively rather than recomputing a power. The returned temperature is the one prepared for a possible next decision, not the temperature used by the last recorded decision.
Reproducible random draws. Each call advances integer state $z$ by $z\leftarrow48271z\bmod2147483647$. Valid seeds lie in $[1,2147483646]$. For directions, parity of $z-1$ chooses left or right; its possible range has an even number of elements. For uphill acceptance, $U=z/2147483647$, which is strictly between zero and one. Only uphill trials consume that second draw. The generator is deterministic and its finite-resolution values approximate the ideal independent uniform model; a fixed seed is for reproducibility, not a proof of a stochastic convergence theorem.
flowchart TD
A["Validate input and initialize one RNG"] --> B{"Initial evaluation budget available?"}
B -->|No| C["Return EVAL_LIMIT; no value"]
B -->|Yes| D["Evaluate start; initialize current and best"]
D --> E{"Proposal cap reached?"}
E -->|Yes| F["Return STEP_LIMIT"]
E -->|No| G{"Temperature below floor?"}
G -->|Yes| H["Return COOLED"]
G -->|No| I{"Cooling previously stalled?"}
I -->|Yes| J["Return TEMP_STALLED"]
I -->|No| K{"Another evaluation fits budget?"}
K -->|No| L["Return EVAL_LIMIT with best so far"]
K -->|Yes| M["Draw unit direction; clamp; evaluate candidate"]
M --> N{"Candidate energy no greater?"}
N -->|Yes| O["Accept candidate"]
N -->|No| P["Draw open-interval U"]
P --> Q{"log U below negative delta over T?"}
Q -->|Yes| O
Q -->|No| R["Reject; current stays unchanged"]
O --> S["Update best only if current improves it"]
S --> T["Record decision; cool; detect rounded stall"]
R --> T
T --> E
Worked Example with a Full Trace
Minimize the table
$$[E(0),E(1),E(2),E(3),E(4),E(5)]=[0,4,1,-5,0,2].$$
Use start $0$, seed $84$, $12$ proposal decisions, $T_0=5$, cooling factor $\alpha=0.85$, floor $T_{\min}=0.01$, and evaluation budget $1000$. This allows all $12$ decisions. Initial evaluation establishes current $0:0$ and best $0:0$, using one call and no random draws.
The first raw proposal draw is $4054764$. Since $(4054764-1)\bmod2=1$, propose direction $+1$, state $1$, energy $4$. The next raw draw is $306501167$, giving $U\approx0.142725728$. At $T=5$, uphill acceptance probability is $\exp(-4/5)\approx0.449328964$, so the move is accepted. Current becomes energy $4$ while the best remains energy $0$.
The full trace below records every decision. A state cell is index:energy. Temperatures, probabilities, and draws are displayed to nine decimal places; decisions use full precision. Probabilities are shown for explanation and are not explicitly evaluated by the code. A dash means no acceptance draw was needed because the proposal was non-worsening. Evaluations count the initial state plus one candidate per row; raw-draw counts include direction and any uphill acceptance draw.
| Step / $T$ | Old state:energy | Candidate:energy / $\Delta E$ | Acceptance probability / draw $U$ | Decision; current | Best | Evaluations / raw draws |
|---|---|---|---|---|---|---|
| $1$ / $5.000000000$ | $0:0$ | $1:4$ / $4$ | $0.449328964$ / $0.142725728$ | Accept; $1:4$ | $0:0$ | $2/2$ |
| $2$ / $4.250000000$ | $1:4$ | $2:1$ / $-3$ | $1.000000000$ / not drawn | Accept; $2:1$ | $0:0$ | $3/3$ |
| $3$ / $3.612500000$ | $2:1$ | $3:-5$ / $-6$ | $1.000000000$ / not drawn | Accept; $3:-5$ | $3:-5$ | $4/4$ |
| $4$ / $3.070625000$ | $3:-5$ | $2:1$ / $6$ | $0.141706170$ / $0.933940833$ | Reject; $3:-5$ | $3:-5$ | $5/6$ |
| $5$ / $2.610031250$ | $3:-5$ | $2:1$ / $6$ | $0.100376913$ / $0.432704608$ | Reject; $3:-5$ | $3:-5$ | $6/8$ |
| $6$ / $2.218526562$ | $3:-5$ | $4:0$ / $5$ | $0.105004918$ / $0.455045928$ | Reject; $3:-5$ | $3:-5$ | $7/10$ |
| $7$ / $1.885747578$ | $3:-5$ | $2:1$ / $6$ | $0.041512445$ / $0.072753978$ | Reject; $3:-5$ | $3:-5$ | $8/12$ |
| $8$ / $1.602885441$ | $3:-5$ | $4:0$ / $5$ | $0.044184797$ / $0.003598873$ | Accept; $4:0$ | $3:-5$ | $9/14$ |
| $9$ / $1.362452625$ | $4:0$ | $3:-5$ / $-5$ | $1.000000000$ / not drawn | Accept; $3:-5$ | $3:-5$ | $10/15$ |
| $10$ / $1.158084731$ | $3:-5$ | $4:0$ / $5$ | $0.013333530$ / $0.637247770$ | Reject; $3:-5$ | $3:-5$ | $11/17$ |
| $11$ / $0.984372022$ | $3:-5$ | $2:1$ / $6$ | $0.002253532$ / $0.001785655$ | Accept; $2:1$ | $3:-5$ | $12/19$ |
| $12$ / $0.836716218$ | $2:1$ | $1:4$ / $3$ | $0.027724327$ / $0.956594814$ | Reject; $2:1$ | $3:-5$ | $13/21$ |
For reproducibility, the raw draws behind those rows are:
| Step | Direction raw draw | Direction | Acceptance raw draw |
|---|---|---|---|
| $1$ | $4054764$ | $1$ | $306501167$ |
| $2$ | $1102988074$ | $1$ | not drawn |
| $3$ | $1922743630$ | $1$ | not drawn |
| $4$ | $662024037$ | $-1$ | $2005622667$ |
| $5$ | $553984703$ | $-1$ | $929226069$ |
| $6$ | $180641810$ | $1$ | $977203690$ |
| $7$ | $1121013635$ | $-1$ | $156237979$ |
| $8$ | $1948399692$ | $1$ | $7728520$ |
| $9$ | $1548717989$ | $-1$ | not drawn |
| $10$ | $2112811302$ | $1$ | $1368479165$ |
| $11$ | $1260791995$ | $-1$ | $3834665$ |
| $12$ | $419520573$ | $-1$ | $2054271720$ |
After step $3$, state $3$ with energy $-5$ is saved as best. Step $8$ accepts a worsening move to energy $0$, and step $11$ accepts a rare worsening move to energy $1$. The current energy therefore does not decrease monotonically. The incumbent remains $3:-5$ through both excursions and the final rejection.
The final result is STEP_LIMIT, current state $2$ with energy $1$, best state $3$ with energy $-5$, $13$ evaluations, $6$ acceptances, $3$ accepted uphill moves, $6$ rejections, $6$ actual state changes, and $21$ raw draws. Final generator state is $2054271720$, and the temperature prepared for a possible next proposal is approximately $0.711208786$. A full scan of this tiny table independently confirms that $-5$ is its global minimum; the stopping status did not prove that.
Trace It by Hand
At step $7$, current state is $3$ with energy $-5$, the candidate is $2$ with energy $1$, temperature is about $1.885747578$, and $U\approx0.072753978$. Predict the decision and whether temperature changes.
Reveal: $\Delta E=6$ and $\exp(-6/T)\approx0.041512445$, below the draw. Reject the proposal, retain current and best at $3:-5$, and still cool to about $1.602885441$ for step $8$. That next step proposes state $4$ with energy $0$ and accepts because its much smaller draw, about $0.003598873$, is below probability $0.044184797$.
Now cap total evaluations at $3$. Only the initial state and the first two candidates fit. Return EVAL_LIMIT with current $2:1$, best $0:0$, and three raw draws; no draw for step $3$ is consumed. The saved best is useful even though the run has not reached a better basin.
Implementation in Python, C++, and Java
Each complete program implements the same bounded integer-state annealing policy. It proposes exactly one unit direction per decision, retains an outward boundary step as a self-proposal, and uses cached current energy for the difference. It deliberately evaluates a candidate even when it equals current, keeping the evaluation contract simple. The objective is an integer table; replacing it with an implicit evaluator requires retaining feasible proposals and handling its own errors and costs.
Input contract. The non-null energy collection has $1\le n\le256$ integer values in $[-10^9,10^9]$. Start is in $[0,n-1]$, seed in $[1,2147483646]$, requested proposal count in $[0,2000]$, and total evaluation budget in $[0,10^6]$. Temperatures and cooling must be finite, with $0\lt T_{\min}\le T_0\le10^9$ and $0\lt\alpha\lt1$. Invalid sizes, ranges, seeds, or schedule values are rejected. Energies are copied; the caller's input is preserved, and repeated calls reset the generator and all counters.
Result contains status, has_value, current state/energy, best state/energy, temperature prepared for the next proposal, six statistics, final generator state, and decision history. Statistics are [evaluations,accepted,uphill,rejected,changed,rng_draws]. Acceptances include equal-energy moves and self-proposals; changed counts only accepted moves to a different index. The uphill count includes accepted proposals with positive $\Delta E$. A zero-budget result has no meaningful energy: has_value is false, best index is $-1$, and zero energy fields are dummy values.
Each history row contains the temperature used, old state/energy, direction and its raw draw, candidate state/energy, difference, optional acceptance raw draw, decision, resulting current and incumbent, evaluation count, and raw-draw count. An acceptance raw draw of zero is a sentinel for “not used”; actual draws are strictly positive. The table's bounded differences and 64-bit C++/Java arithmetic avoid integer overflow in $\Delta E$ and in random-generator multiplication.
STEP_LIMIT means the requested number of proposal decisions finished. COOLED means the next temperature is strictly below the floor; equality with the floor still allows a decision. TEMP_STALLED means multiplication rounded back to the same positive temperature. EVAL_LIMIT means a required objective call is unavailable. After initialization, precedence is step count, floor, stalled cooling, then evaluation budget. Thus a final step can report STEP_LIMIT even when its cooling also crosses the floor. Underflow to zero is detected by the floor check before any division by temperature. None of these statuses certifies a local or global minimum.
Every initialized run with $I$ recorded decisions uses exactly $I+1$ objective calls. A zero-budget run uses none. Every decision consumes one raw direction draw; each positive-difference trial consumes one additional acceptance draw, so $I\le D_{\mathrm{rng}}\le2I$. If a candidate is worse, even a near-certain rejection consumes that draw. These rules are part of reproducibility; warm and cold runs with the same seed can diverge in both current states and later draw consumption.
Run Python 3, C++14 or newer, or Java 17 or newer, saving Java as Main.java. Compile C++ with -ffp-contract=off and without unsafe floating-point transformations for the tested arithmetic order. Java's explicit decimal formatting helper uses the exact binary64 value and half-even rounding so its printed scientific notation matches Python and C++, including subnormals. Acceptance and temperature calculations themselves remain binary64; logarithms are subject to the platform's numerical accuracy.
Expected standard output, identical in all three languages:
worked: STEP_LIMIT, current=2:1, best=3:-5evaluations=13, accepted=6, uphill=3, rejected=6, changed=6, rng_draws=21, next_T=7.112088e-01, rng_state=2054271720i=0, x=0, E=0i=1, T=5.000000e+00, candidate=1:4, delta=4, u=1.427257e-01, accept=YES, x=1, E=4, best=0:0i=2, T=4.250000e+00, candidate=2:1, delta=-3, u=-, accept=YES, x=2, E=1, best=0:0i=3, T=3.612500e+00, candidate=3:-5, delta=-6, u=-, accept=YES, x=3, E=-5, best=3:-5i=4, T=3.070625e+00, candidate=2:1, delta=6, u=9.339408e-01, accept=NO, x=3, E=-5, best=3:-5i=5, T=2.610031e+00, candidate=2:1, delta=6, u=4.327046e-01, accept=NO, x=3, E=-5, best=3:-5i=6, T=2.218527e+00, candidate=4:0, delta=5, u=4.550459e-01, accept=NO, x=3, E=-5, best=3:-5i=7, T=1.885748e+00, candidate=2:1, delta=6, u=7.275398e-02, accept=NO, x=3, E=-5, best=3:-5i=8, T=1.602885e+00, candidate=4:0, delta=5, u=3.598873e-03, accept=YES, x=4, E=0, best=3:-5i=9, T=1.362453e+00, candidate=3:-5, delta=-5, u=-, accept=YES, x=3, E=-5, best=3:-5i=10, T=1.158085e+00, candidate=4:0, delta=5, u=6.372478e-01, accept=NO, x=3, E=-5, best=3:-5i=11, T=9.843720e-01, candidate=2:1, delta=6, u=1.785655e-03, accept=YES, x=2, E=1, best=3:-5i=12, T=8.367162e-01, candidate=1:4, delta=3, u=9.565948e-01, accept=NO, x=2, E=1, best=3:-5cold: STEP_LIMIT, current=0:0, best=0:0evaluations=13, accepted=7, uphill=0, rejected=5, changed=0, rng_draws=17, next_T=1.422418e-10, rng_state=1368479165i=0, x=0, E=0i=1, T=1.000000e-09, candidate=1:4, delta=4, u=1.427257e-01, accept=NO, x=0, E=0, best=0:0i=2, T=8.500000e-10, candidate=1:4, delta=4, u=8.953473e-01, accept=NO, x=0, E=0, best=0:0i=3, T=7.225000e-10, candidate=0:0, delta=0, u=-, accept=YES, x=0, E=0, best=0:0i=4, T=6.141250e-10, candidate=0:0, delta=0, u=-, accept=YES, x=0, E=0, best=0:0i=5, T=5.220062e-10, candidate=0:0, delta=0, u=-, accept=YES, x=0, E=0, best=0:0i=6, T=4.437053e-10, candidate=0:0, delta=0, u=-, accept=YES, x=0, E=0, best=0:0i=7, T=3.771495e-10, candidate=1:4, delta=4, u=4.550459e-01, accept=NO, x=0, E=0, best=0:0i=8, T=3.205771e-10, candidate=0:0, delta=0, u=-, accept=YES, x=0, E=0, best=0:0i=9, T=2.724905e-10, candidate=0:0, delta=0, u=-, accept=YES, x=0, E=0, best=0:0i=10, T=2.316169e-10, candidate=1:4, delta=4, u=3.598873e-03, accept=NO, x=0, E=0, best=0:0i=11, T=1.968744e-10, candidate=0:0, delta=0, u=-, accept=YES, x=0, E=0, best=0:0i=12, T=1.673432e-10, candidate=1:4, delta=4, u=6.372478e-01, accept=NO, x=0, E=0, best=0:0cooled: COOLED, current=0:0, best=0:0evaluations=2, accepted=0, uphill=0, rejected=1, changed=0, rng_draws=2, next_T=5.000000e-01, rng_state=306501167i=0, x=0, E=0i=1, T=1.000000e+00, candidate=1:4, delta=4, u=1.427257e-01, accept=NO, x=0, E=0, best=0:0evaluation cap: EVAL_LIMIT, current=2:1, best=0:0evaluations=3, accepted=2, uphill=1, rejected=0, changed=2, rng_draws=3, next_T=3.612500e+00, rng_state=1102988074i=0, x=0, E=0i=1, T=5.000000e+00, candidate=1:4, delta=4, u=1.427257e-01, accept=YES, x=1, E=4, best=0:0i=2, T=4.250000e+00, candidate=2:1, delta=-3, u=-, accept=YES, x=2, E=1, best=0:0zero steps: STEP_LIMIT, current=0:0, best=0:0evaluations=1, accepted=0, uphill=0, rejected=0, changed=0, rng_draws=0, next_T=5.000000e+00, rng_state=84i=0, x=0, E=0zero budget: EVAL_LIMIT, current=NONE, best=NONEevaluations=0, accepted=0, uphill=0, rejected=0, changed=0, rng_draws=0, next_T=5.000000e+00, rng_state=84singleton: STEP_LIMIT, current=0:7, best=0:7evaluations=3, accepted=2, uphill=0, rejected=0, changed=0, rng_draws=2, next_T=3.612500e+00, rng_state=306501167i=0, x=0, E=7i=1, T=5.000000e+00, candidate=0:7, delta=0, u=-, accept=YES, x=0, E=7, best=0:7i=2, T=4.250000e+00, candidate=0:7, delta=0, u=-, accept=YES, x=0, E=7, best=0:7temperature stall: TEMP_STALLED, current=0:7, best=0:7evaluations=2, accepted=1, uphill=0, rejected=0, changed=0, rng_draws=1, next_T=4.940656e-324, rng_state=4054764i=0, x=0, E=7i=1, T=4.940656e-324, candidate=0:7, delta=0, u=-, accept=YES, x=0, E=7, best=0:7checks = PASS
Complexity, Correctness, and Invariants
Let $I$ be completed proposal decisions, $C_q$ proposal-generation cost, $C_E$ objective-evaluation cost, and $C_s$ the cost of applying or copying a state. The core runtime is $O(I(C_q+C_E+C_s+1))$ plus one initial evaluation; unlike steepest descent, a decision does not scan every neighbor. If objective differences can be computed incrementally, charge their actual cost and verify that cached energy stays consistent.
For this table adapter, input validation and copying take $O(n)$, proposals and lookups take constant time, and each decision uses at most two fixed-width random updates plus one logarithm. In the usual machine-arithmetic cost model, actual runtime is $O(n+I)$, and the retained history makes additional space $O(n+I)$. Without logging, the active annealing state is constant-size beyond the energy representation. General configurations and their saved best copies can be much larger.
The requested proposal cap $K$ and evaluation budget $B$ give $I\le\min(K,\max(0,B-1))$ once initialization is accounted for. In exact geometric cooling, with $T_0\ge T_{\min}\gt0$, the number of proposals whose temperatures are at least the floor is
$$J=\left\lfloor\frac{\log(T_{\min}/T_0)}{\log\alpha}\right\rfloor+1.$$
So the ideal floor also bounds $I$ by $J$. The code checks actual successive binary64 products and can stop earlier on rounded stagnation; it does not use the logarithmic formula to choose its iteration count.
Best, average, worst. A zero-budget request performs no objective evaluations but still validates/copies the input. An initialized zero-step request performs one evaluation. For a fixed executed decision count, this adapter's work is linear regardless of acceptances, with one extra draw/logarithm on uphill trials. There is no distribution-free average time to reach a global optimum. In the worst finite run, every allowed proposal is used and the best found state can still be suboptimal. Counters bound search work, not the elapsed time of an expensive evaluator.
The implementation invariants. Current is always a feasible state with its exact cached energy. Every candidate is feasible; a non-worsening candidate is accepted, and an uphill candidate is accepted exactly when the implemented log comparison passes. Rejection changes neither current nor its cached energy. Best energy equals the smallest energy evaluated so far and never increases: any candidate better than best is also better than current and is therefore accepted, while a rejected uphill candidate cannot improve best. Cooling advances once per completed proposal, independent of acceptance. These invariants justify the result and accounting; they do not make current energy monotone or prove global convergence.
Why symmetry appears in the theory. Under ideal independent random draws, exact comparisons, and a fixed positive temperature, define the Boltzmann weights
$$\pi_T(x)=\frac{\exp(-E(x)/T)}{Z_T},\qquad Z_T=\sum_{s\in S}\exp(-E(s)/T).$$
For symmetric $q$, the off-diagonal probability flow satisfies
$$\pi_T(x)q(y\mid x)a_T(x,y)=\frac{q(y\mid x)}{Z_T}\min\bigl(\exp(-E(x)/T),\exp(-E(y)/T)\bigr),$$
which is unchanged when $x$ and $y$ are exchanged. This is detailed balance. For a connected finite state graph with appropriate aperiodicity, the fixed-temperature chain has this stationary distribution. The clamped proposal on the finite line is connected and has boundary self-proposals, giving aperiodicity in that ideal model. No partition function is computed by the code.
As $T$ tends to zero, the mathematical equilibrium distribution concentrates on global minima, but a cooling chain is not automatically at equilibrium at each temperature. Global convergence results require additional hypotheses and sufficiently slow cooling. Classical finite-state results include logarithmic schedules $T_k=c/\log(k+k_0)$ with a sufficiently large, landscape-dependent barrier constant. A short geometric schedule, a positive floor, finite random precision, and a finite budget do not inherit that asymptotic guarantee. In particular, repeated rejection does not prove that no improving neighbor exists, because the algorithm samples one proposal rather than completing a neighborhood scan.
| Method | Work per decision | Working state before optional logs | Characteristic behavior |
|---|---|---|---|
| Strict steepest descent | Up to all $B_N$ neighbor evaluations | Current, best candidate, incumbent | Chooses the best improvement; cannot cross an energy barrier |
| Random-restart local search | Several local runs with reinitialization | One active run, persistent best, generator | Explores other basins through new starts rather than downhill/uphill transition paths |
| Metropolis at fixed temperature | One proposal and one candidate evaluation | Current, best if optimizing, generator | Can sample a Boltzmann target under the ideal symmetric-proposal assumptions; it does not cool |
| Geometrically cooled annealing | $O(C_q+C_E+C_s+1)$ per proposal | Adds a temperature and schedule state | Practical finite heuristic; rejects more uphill proposals as temperature falls |
| This traced table implementation | $O(n+I)$ total with $I$ executed decisions | $O(n+I)$ including copied energies and history | Exact trace contract, guarded temperatures, and separate current/best values |
| Metropolis-Hastings annealing | Adds the forward/reverse proposal ratio | May need proposal-density information | Corrects an asymmetric proposal if the desired interpretation requires it |
| Tabu / iterated local search | Depends on candidate scans, memory, and perturbations | Adds tabu memory or perturbation state | Other ways to escape basins; different acceptance mechanisms |
| Exhaustive minimum of this explicit table | $O(n)$ energy accesses | $O(1)$ beyond input | Exact global answer; preferable when the whole table is already available |
Current always matches its feasible state and cached energy, while best is the lowest energy evaluated anywhere and survives uphill excursions. The acceptance rule is mathematically justified for the stated proposal model; detailed balance and slow-cooling theory need their own assumptions and do not turn a finite stopping status into a global-optimality certificate.
Variants, Pitfalls, and Optimizations
Keep the energy sign consistent. Here lower is better and $\Delta E=E_{\mathrm{candidate}}-E_{\mathrm{current}}$. The uphill exponent is negative. Using $\exp(+\Delta E/T)$ makes uphill values exceed one and reverses the intended preference. For maximizing a score $f$, one consistent transformation is $E=-f$, which gives uphill-energy acceptance $\exp(\Delta f/T)$ when $\Delta f\lt0$.
Return best, not merely the last state. In the worked run, current ends at energy $1$ despite having visited $-5$. Preserve the incumbent separately, and copy it safely if states are mutable. An in-place proposal must also be undone on rejection, including all cached deltas, feasibility metadata, and random-key bookkeeping. The sample avoids that mutation problem by evaluating an integer candidate before assigning current.
Do not silently change proposal symmetry. On three indices, choosing uniformly among only valid adjacent neighbors gives $q(1\mid0)=1$ but $q(0\mid1)=1/2$. Even on a flat energy table, that walk does not have a uniform stationary distribution. The code instead leaves half the endpoint proposals as self-proposals, making the off-diagonal probabilities symmetric. For a general asymmetric proposal, the Boltzmann-target Metropolis-Hastings rule is
$$a_T(x,y)=\min\left(1,\exp(-\Delta E/T)\frac{q(x\mid y)}{q(y\mid x)}\right),$$
with the required proposal support and model assumptions. Resampling until feasible or applying a many-to-one repair can change those probabilities. A heuristic may still explore with such a proposal, but the simple symmetric-proposal stationary-distribution argument no longer follows automatically.
Numerical stability and random resolution are separate issues. Log space avoids explicitly underflowing the exponential. It cannot create arbitrarily small random draws: this teaching generator's acceptance values are $z/P$, so the smallest is $1/P\approx4.66\times10^{-10}$. An uphill acceptance probability below that cannot pass a draw in this implementation, even though the ideal continuous-uniform rule assigns a positive chance. Finite precision also affects logs and the final comparison near a threshold. Use a suitable higher-resolution random source and precision policy when those effects matter; reproducibility tests do not establish independent sampling.
Choose and scale temperatures deliberately. If a representative uphill change is $d\gt0$ and a desired initial acceptance is $p_0\in(0,1)$, the ideal calibration is $T_0=-d/\log p_0$. Adding a constant to all energies leaves acceptance unchanged; multiplying energies by a positive constant requires scaling temperatures by the same constant to preserve the ratios. Excessive initial temperature may spend most time wandering, while a very low start or rapid cooling can trap the chain. The cold fixture at $T_0=10^{-9}$ never leaves the initial basin in its twelve decisions.
Cooling is a policy, not a proof. Geometric schedules are convenient finite heuristics. Multiple proposals at each temperature, reheating, independent restarts, adaptive proposals, and logarithmic cooling are different policies with different assumptions and costs. Count rejected proposals in the cooling clock if that is the chosen schedule. Do not accept a temperature of zero and divide by it; the implementation requires a positive floor and reports both underflow-driven cooling and rounded temperature stalls honestly.
The objective and constraints must remain meaningful. Proposals should preserve feasibility or use an explicitly analyzed feasibility policy. A penalty objective does not automatically make the returned incumbent feasible for hard constraints. Noisy energy estimates can corrupt both acceptance and the best-ever comparison; the deterministic exact-energy reasoning then needs replacement. Likewise, a tiny energy change or a long rejection streak is not a general certificate of convergence.
Use a consistent minimization sign, keep best separate from current, and preserve or correct the proposal probabilities. Positive temperatures, log-space arithmetic, and resource guards prevent numerical failures, while a finite cooled run still supplies a best-found candidate rather than a proof of global optimality.
Practice Problems and Real-World Use
Start with the six-state energy table and compare each acceptance against an independent high-precision probability calculation. Record rejected proposals as well as accepted moves, verify the current-state cache, and check that the incumbent never becomes worse. Include negative energies, equal moves to different states, boundary self-proposals, zero budgets, exact budget exhaustion, underflow, and temperature stagnation.
The programs were checked on 8,592 inputs per language: $8{,}565$ valid cases and $27$ invalid requests. The corpus includes $6{,}564$ exhaustive combinations of energy arrays over $\{-1,0,1\}$ of lengths $1$ through $5$, every starting state, two seeds, and two starting temperatures, plus randomized and boundary fixtures. An independent oracle used jump-ahead modular RNG values, high-precision exponential comparisons, and exact rational temperature products. It checked $131{,}395$ temperature updates and incumbent-preservation steps, $52{,}492$ high-precision acceptance comparisons, and $11{,}814$ safely rejected large-ratio trials; it observed $9{,}320$ accepted uphill proposals. Separately, $22{,}959$ ideal transition-matrix entries were checked for proposal symmetry and detailed balance. Python, C++, and Java matched all outcomes, complete histories, counters, raw RNG states, and binary64 temperatures on the corpus and repeated runs, preserved inputs, and the C++ corpus passed address/undefined-behavior instrumentation.
For primary background, see Carnegie Mellon's simulated-annealing notes. For the distinction between practical cooling and asymptotic global convergence, see Bruce Hajek's paper on cooling schedules. The exact proposal, draw-consumption rules, schedule, finite-precision limitations, resource contract, and trace in this lesson are specified directly here.
| Practice problem or application | Concrete task | Idea tested | Difficulty |
|---|---|---|---|
| Six-state barrier | Reproduce the complete twelve-decision trace and cold comparison | Uphill acceptance, cooling, and persistent best | Easy |
| Flat bounded line | Set all energies equal and inspect self-proposals and equal moves | Symmetry, acceptance counts versus actual changes | Easy |
| Boundary proposal laboratory | Compare clamped directions with forced-inward valid-neighbor sampling | Why proposal probabilities matter | Medium |
| Tour optimization | Use a well-defined symmetric 2-opt proposal on feasible tours | Incremental energy differences and rejection undo | Medium |
| Scheduling or placement | Define legal perturbations and an energy with explicit hard/soft constraints | Feasibility, temperature scale, and candidate quality | Hard |
| Discrete parameter tuning | Compare several seeds and schedules against an exact small-instance minimum | Evaluation budgets, failure modes, and empirical success rates | Medium |
| Numerical stress laboratory | Test subnormal temperatures, near-one cooling, and extreme energy ratios | Positive-temperature guards, log comparisons, and finite RNG resolution | Hard |
| Cooling-schedule study | Compare geometric, per-temperature batches, and slow logarithmic schedules | Mixing time, practical budgets, and the limits of global-convergence claims | Hard |