Winning Kaggle the Reproducible Way

Interactive chapter

This page shows the chapter notebook with its saved results. Constructed examples do not measure competition gains.

Chapter 54: Combinatorial Optimization

Try the decision first

With a fixed number of evaluated moves, does a hotter starting temperature or a better starting tour give the shorter final tour?

Write your expectation before opening the worked results. Use the chapter activity to compare supported inputs. To change the calculation itself, run the notebook on GitHub.

Your answers stay on this page until you download them. They are not sent to a server.

Open the chapter activity

Worked notebook and saved results

With a fixed number of evaluated moves, does a hotter starting temperature or a better starting tour give the shorter final tour?

A temperature that is too hot spends the budget accepting moves that undo progress, and one that is too cold may stop improving early. Choosing it from sampled moves, then comparing starts under the same move count, is how to tell which wastes the budget.

Constructed instances and a single budget: with a much longer budget the random start would catch up. The difference between a random start's low acceptance settings is within noise here, so no sharp optimum is claimed for it.

The experiment

Eight constructed instances of 150 random cities. Simulated annealing uses 2-opt moves (reverse a segment) and geometric cooling over exactly 60,000 evaluated moves. The starting temperature is solved by bisection so that the mean probability of accepting a sampled worsening move, mean(exp(-delta/T)), equals the control; the final temperature is 1% of the mean edge length of the nearest-neighbour tour. Each instance is annealed from a random tour and from the nearest-neighbour tour. The score is the final (current) tour length divided by the length of the nearest-neighbour tour, and every returned tour is checked to be a valid permutation with a recomputed length.

Calculation code
import math
import random

import numpy as np

from kaggle_companion.activities._common import clean

SEED = 54
CITIES, BUDGET, INSTANCES = 150, 60000, 8   # every run gets the same 60,000 evaluated 2-opt moves
END_TEMPERATURE = 0.01                      # final temperature, as a fraction of the mean edge of the nearest-neighbour tour


def make_instance(seed):
    """Random Euclidean cities as a distance table (nested lists: fastest for scalar lookups)."""
    points = np.random.default_rng(seed).random((CITIES, 2))
    return np.sqrt(((points[:, None] - points[None]) ** 2).sum(-1)).tolist()


def tour_length(D, tour):
    return sum(D[tour[i]][tour[(i + 1) % CITIES]] for i in range(CITIES))


def nearest_neighbour_tour(D):
    """Cheap legal initial tour: always walk to the closest unvisited city."""
    left, tour = set(range(1, CITIES)), [0]
    while left:
        nxt = min(left, key=lambda j: D[tour[-1]][j])
        tour.append(nxt)
        left.remove(nxt)
    return tour


def two_opt_delta(D, tour, i, j):
    """Change in length if tour[i..j] is reversed (swap two edges)."""
    a, b, c, d = tour[i - 1], tour[i], tour[j], tour[(j + 1) % CITIES]
    return D[a][c] + D[b][d] - D[a][b] - D[c][d]


def sample_worsening(D, tour, rng, count=300):
    """Worsening 2-opt deltas sampled from the current tour."""
    out = []
    while len(out) < count:
        i, j = sorted(rng.sample(range(1, CITIES), 2))
        delta = two_opt_delta(D, tour, i, j)
        if delta > 0:
            out.append(delta)
    return out


def acceptance(deltas, temperature):
    """Mean probability that a sampled worsening move is accepted: mean of exp(-delta / T)."""
    return sum(math.exp(-d / temperature) for d in deltas) / len(deltas)


def solve_temperature(deltas, target):
    """The chapter's recipe: the acceptance is monotone in T, so bisect for the T that gives the target."""
    lo, hi = 1e-9, 1e3
    for _ in range(60):
        mid = math.sqrt(lo * hi)
        lo, hi = (mid, hi) if acceptance(deltas, mid) < target else (lo, mid)
    return hi


def anneal(D, tour, target, rng, end_temperature):
    """Geometric cooling from the solved T0 to end_temperature over exactly BUDGET evaluated moves."""
    tour = list(tour)
    length = tour_length(D, tour)
    t0 = solve_temperature(sample_worsening(D, tour, rng), target)
    temperature, cool = t0, (end_temperature / t0) ** (1 / BUDGET) if t0 > end_temperature else 1.0
    for _ in range(BUDGET):
        i, j = sorted(rng.sample(range(1, CITIES), 2))
        delta = two_opt_delta(D, tour, i, j)
        if delta < 0 or rng.random() < math.exp(-delta / temperature):
            tour[i:j + 1] = tour[i:j + 1][::-1]
            length += delta
        temperature *= cool
    assert sorted(tour) == list(range(CITIES)), "invalid tour"          # report only valid completed tours
    assert abs(length - tour_length(D, tour)) < 1e-6, "tracked length drifted"
    return length, t0


def run(target_acceptance):
    ratio = {"random": [], "nn": []}
    t0_scaled = {"random": [], "nn": []}
    naive_acceptance = []
    for k in range(INSTANCES):
        D = make_instance(SEED * 100 + k)
        rng = random.Random(SEED * 100 + k)
        nn = nearest_neighbour_tour(D)
        base = tour_length(D, nn)                                    # reference: the nearest-neighbour tour, no search
        edge = base / CITIES
        shuffled = list(range(CITIES))
        rng.shuffle(shuffled)
        for name, start in (("random", shuffled), ("nn", nn)):
            length, t0 = anneal(D, start, target_acceptance, rng, END_TEMPERATURE * edge)
            ratio[name].append(length / base)
            t0_scaled[name].append(t0 / edge)
        # the shortcut the chapter warns about: set T from the mean worsening delta so that exp(-mean/T) equals the target
        deltas = sample_worsening(D, nn, rng)
        naive_t = -(sum(deltas) / len(deltas)) / math.log(target_acceptance)
        naive_acceptance.append(acceptance(deltas, naive_t))
    return clean({
        "target_acceptance": target_acceptance,
        "ratio": {k: float(np.mean(v)) for k, v in ratio.items()},
        "per_instance": ratio,
        "t0_over_edge": {k: float(np.mean(v)) for k, v in t0_scaled.items()},
        "naive_acceptance": float(np.mean(naive_acceptance)),
        "cities": CITIES, "budget": BUDGET, "instances": INSTANCES,
    })

Measure it across the control

The control is target starting acceptance of a worsening move. Each value below runs the whole experiment from the generator up.

Calculation code
import pandas as pd
from kaggle_companion.activities.ch54 import explain

VALUES = [0.01, 0.1, 0.5, 0.9]
results = {value: run(value) for value in VALUES}
table = pd.DataFrame({str(v): explain(results[v], v)['metrics'] for v in VALUES})
print(table.to_string())
                                                  0.01         0.1         0.5          0.9
Random start, final / NN length                  0.888       0.878       0.904        0.936
Nearest-neighbour start, final / NN length       0.856       0.902       0.925        0.940
Saving of the better start                       0.144       0.122       0.096        0.064
Naive-T acceptance                               0.075       0.188       0.536        0.901
Solved T0, random start                     0.03 edges  0.40 edges  4.21 edges  36.92 edges

Look at it

The figure shows the default setting, target starting acceptance of a worsening move = 0.5.

Calculation code
import matplotlib.pyplot as plt
from kaggle_companion.activities.ch54 import draw, NCOLS, HEIGHT

DEFAULT = 0.5
fig, axes = plt.subplots(1, NCOLS, figsize=(6.6 if NCOLS == 1 else 10.4, HEIGHT), layout='constrained')
draw(axes, results[DEFAULT], DEFAULT)
plt.show()
Saved figure from this chapter calculation

What the result says

With a target acceptance of 0.50 and 60,000 moves, annealing from a random tour ends at 0.904 of the nearest-neighbour length and from the nearest-neighbour tour at 0.925, so the random start is shorter and 1 - 0.904 = 0.096 is the saving over no search. The temperature solved for this acceptance is 4.21 mean edge lengths for the random tour and 14.80 for the nearest-neighbour tour. Setting T from the mean worsening delta gives an acceptance of 0.536 instead of 0.50.

Apply this to your competition

Book location: Chapter 54, Simulated Annealing: The Most Robust Starting Point.

Technical corrections

Apply a lesson from this chapter

Select a lesson whose conditions match your project. State how you would test it and what would make you reject it.

Reduce search space before optimizing enumeration

Conditions

  • an exact reconstruction has a huge but prunable candidate space
  • an exact search still has an enormous ambiguous space

Action

  • Proposed adaptation of source observation: Apply valid constraints first; profile enumeration; consider a compiled implementation once the remaining space is tractable.
  • Proposed conditional application: Use manual ambiguity reduction before optimized compiled brute force; the author reports evaluating 5x10^14 solutions in 20–30 minutes on a 24-CPU VM.

Check

  • Proposed verification: Benchmark candidate throughput and verify optimized outputs against a small reference search.
  • Proposed check: Verify reduced and exhaustive searches return identical constraint-satisfying candidates on small solvable instances; benchmark elapsed time and CPUs.

Limits

  • Extreme reported throughput depends on omitted implementation details; runtime is not a general hardware guarantee.
  • Leaderboard-derived scoring and ranking constraints drove this reconstruction; ordinary prediction rarely permits that search.
  • The reported runtime is specific to the reduced search and 24-CPU VM.

Find source links and value grades in the lesson atlas

Trade solver iterations against submission time explicitly

Conditions

  • probabilistic inference must fit a time-limited submission

Action

  • Proposed adaptation of source observation: Benchmark iteration and covariance-sample counts against score and total runtime.

Check

  • Proposed verification: Measure convergence and uncertainty quality on identical inputs for each budget.

Limits

  • The 0.001 cost is source-specific; convergence and approximation error depend on the problem.

Find source links and value grades in the lesson atlas

Decompose slideshow construction before local reversal search

Conditions

  • combinatorial ordering includes pairable items and expensive transitions

Action

  • Proposed adaptation of source observation: Build strong subsequences, use pairable items as connectors, then run score-increasing reversal moves.

Check

  • Proposed verification: Check all item-use constraints and recompute objective deltas against full scoring.

Limits

  • Matching and penalty details are omitted; source scores do not guarantee global optimality.

Find source links and value grades in the lesson atlas

Choose iterative solver depth against submission runtime

Conditions

  • an iterative Bayesian solver competes with a fixed notebook deadline

Action

  • Proposed conditional application: Compare four and eight solver iterations; the author reduced eight to four for a nine-hour limit and reports a 0.001 score cost.

Check

  • Proposed check: Sweep iteration counts on fixed planets; measure predictive score, posterior stability and total training-plus-inference time.

Limits

  • Four iterations are the released runtime compromise, not the winning submitted configuration.
  • Metric name and evaluation split for 0.001 are unspecified.

Find source links and value grades in the lesson atlas

Relax distance bias as a delivery schedule progresses

Conditions

  • greedy drone scheduling starts near a fixed origin

Action

  • Proposed conditional application: Multiply shipment scores by distance preference that fades toward one as elapsed time reaches fifty-five percent of total time.

Check

  • Proposed check: Simulate fixed demand instances with fading versus fixed/no distance bias; compare completed orders and travel/time constraints.

Limits

  • 0.55 is heuristic; initial origin may not represent later route costs and contribution not isolated.

Find source links and value grades in the lesson atlas

Audit assumptions when a consistency search yields no solution

Conditions

  • candidate solutions match scores but violate ranking constraints

Action

  • Proposed conditional application: Recheck assumptions and implementation before discarding the search formulation.

Check

  • Proposed check: Construct small known-answer cases; check score and ranking constraints separately and rerun after each implementation correction.

Limits

  • The source does not identify the defective assumption or provide a reproducible bug fix.
  • A no-solution outcome can also indicate inconsistent constraints, not necessarily software error.

Find source links and value grades in the lesson atlas