Track relaxation — convergence and tuning¶
This page covers how the solve converges, what the Chebyshev acceleration buys (with
measured numbers), the relax_sep_every pitfall, and tuning guidance including the
dead ends that were measured so you don’t have to retry them.
Why plain Jacobi needed ~150 sweeps¶
Three effects compound to make the plain solve slow:
Diffusive propagation. Every constraint is local (a bead and its neighbours, or one bead pair), so corrections propagate along the chain roughly one bead per sweep — diffusively. A deep initial penetration between two arcs needs a long-range shape change (whole arcs must migrate apart), and plain Jacobi delivers that change at diffusion speed.
Deep initial penetrations. Raw generated centerlines can start far inside the separation target, so the total displacement to resolve is many multiples of the per-sweep correction.
The hairpin force balance. Near tight hairpins, separation (pushing beads apart), bending (rounding the apex), and spacing (keeping edge lengths) settle into a slowly-tightening force balance rather than a clean fixed point — each sweep makes a small net gain against the others, and convergence is asymptotic.
Four tracks whose raw constant-spacing centerlines are deeply self-overlapping, evolving
from the raw frame (relax_enable=False) through the pure iterative solve
(sweep k / 50, smoothing tail off) and then through the post-solve smoothing tail
(pass p / 5). Because the solve is a fixed, deterministic launch sequence, each frame
is generated afresh with relax_iters=k and is a true snapshot of the same
trajectory (the raw centerlines are asserted bit-identical across two generations). Watch
the deep initial penetrations migrate apart diffusively — a whole-arc shape change that
plain Jacobi delivers only one bead per sweep, which is exactly why the plain solve
needed so many. Regenerate with python -m viz.render_relaxation_video (needs ffmpeg).
Chebyshev acceleration (see Track relaxation — the solver) attacks exactly the first and third effects: it extrapolates along the low-frequency, long-wavelength modes that diffusion is slow on.
What acceleration buys — measured¶
All numbers below are E=4096 fixed seeds on CUDA. Two regimes: defaults is the
library default config; hard is the benchmark regime half_width=0.5,
scale=10, spacing=0.30.
Yield vs sweep count, old solver (plain Jacobi, relax_accel="none",
relax_sep_every=40):
Sweeps |
25 |
50 |
75 |
100 |
150 |
|---|---|---|---|---|---|
hard |
0.878 |
0.970 |
0.987 |
0.994 |
0.997 |
defaults |
0.627 |
0.871 |
0.951 |
0.978 |
0.993 |
Yield vs sweep count, new solver (Chebyshev, relax_sep_every=20):
Sweeps |
25 |
40 |
50 |
150 |
|---|---|---|---|---|
hard |
0.988 |
0.999 |
0.9995 |
1.000 |
defaults |
0.958 |
0.996 |
0.999 |
0.999 |
Measured yield vs sweep count in the defaults regime (E=2048, CUDA): the new Chebyshev solver at 50 sweeps clears what the old plain-Jacobi solver needed 150 for.¶
Quality at the new 50-sweep operating point — which now includes the post-solve
smoothing tail (see Track relaxation — the solver) — matches or beats the old 150-sweep point on
spacing, smoothness, and minimum radius. The smoothing tail trades a hair of gate margin
(still comfortably above the 1.0 · hw gate) for markedly lower curvature noise
(defaults regime):
Metric |
New @ 50 |
Old @ 150 |
|---|---|---|
Spacing RMS |
0.0049 |
0.0086 |
Curvature-difference smoothness |
0.169 |
0.251 |
Min Menger radius |
1.35 · hw |
1.16 · hw |
Gate-margin 5th percentile |
1.01 · hw |
1.03 · hw |
Warmed wall-clock per generate() call at E=4096: defaults 15.9 → 10.5 ms, hard
53.5 → 19.5 ms.
Confirmed on a disjoint seed batch (seed 100000): new@50 = 0.9990 / 0.9990 vs old@150 = 0.9973 / 0.9924 (hard / defaults).
Identical seeds through the old solver at 150 sweeps (top) and the new solver at 50 (bottom), in the benchmark regime. The accelerated solve converges to the same fixed point — the tracks are visually indistinguishable.¶
The relax_sep_every pitfall¶
In the cached separation mode, the broadphase candidate cache is built at sweep 0 and
refreshed only at multiples of relax_sep_every. Any run shorter than that never
refreshes the cache mid-solve and silently misses contacts that emerge during the run
— beads that were outside the broadphase radius at sweep 0 can drift into contact and
never receive a separation push. Under the old default of 40 this produced a measured
yield cliff between 40 and 50 sweeps: a 40-sweep run got exactly one broadphase build
(at sweep 0), a 50-sweep run got a refresh at sweep 40. The default is now
relax_sep_every=20 so the accelerated 50-sweep solve refreshes mid-solve (at sweeps
20 and 40). If you shorten relax_iters, keep relax_sep_every comfortably below
it.
Tuning guidance¶
Raise
relax_itersfirst if yield sags. Cost is linear in sweeps and it does not restrict shapes. The current default of 50 is calibrated for ~0.999 end-to-end yield at the library defaults.Don’t push the Chebyshev knobs.
relax_cheby_rho >= 0.99andrelax_cheby_start <= 5were both measured to overshoot on the nonsmooth separation contacts and reduce yield.relax_cheby_gammabelow ~0.9 over-damps and gives back the acceleration. The defaults (rho 0.98, gamma 0.9, start 8) are the measured operating point.Keep
relax_sep_everywell belowrelax_iters(see the pitfall above).Tune the smoothing tail with
relax_smooth_passes(default 5) andrelax_smooth_spacing_iters(default 10). The Taubin passes remove the sub-R_mincurvature noise the bending guard’s deadband cannot see; the spacing polish restores bead uniformity afterwards. In the prototype (512 tracks) the default tail cut curvature-difference RMS ~33% on average (~51% on the worst track) with zero validity loss and improved spacing RMS. Set both to 0 to disable the tail entirely. Raisingrelax_smooth_passessmooths harder but eats into the gate margin — the Taubin passes are shrink-free but not curvature-preserving, so watchthickness_rel_p05.
Measured dead ends¶
The following were tried and measured to be worse. They are recorded here so future tuners do not retry them.
Constant over-relaxation / SOR. A constant position-level omega of 1.3–1.5, or
relax_sep_relax/relax_spc_relax>= 1.5, collapses yield to 0.31–0.75. Separation contacts switch on and off between sweeps, and constant over-relaxation amplifies exactly that switching noise. Chebyshev’s decaying omega schedule combined with the gamma under-relaxation is the stable variant of the same idea.Raising
relax_margin. It buys yield but costs 20–40% spacing-RMS quality — the larger target distorts the bead chain everywhere, not just at contacts. Prefer more sweeps.Bend-relax decay schedules. Ramping
relax_bend_relaxdown over the solve never beat the constant default of 1.5 at short sweep budgets.Weakening the gate coupling — widening the exclusion band or capping near-band pair targets at the chord-feasible distance — collapses yield (defaults-regime 0.99 → 0.74 at band+1). See Gate coupling — a load-bearing overshoot for the full account.
Plain (non-Taubin) Laplacian smoothing in the post-solve tail. A plain Laplacian (
x += lambda * L(x)with no compensating negative half-step) shrinks the curve — it pulls corners inward, and in the prototype 10 plain-Laplacian passes collapsed yield to 0.12 by dragging corners back through the thickness gate. The Taubin two-step (mu < -lambda) is the shrink-free variant that is actually shipped; do not swap it for a plain Laplacian.