Track relaxation — overview¶
Relaxation is the second stage of the runtime pipeline. It sits between the constant-spacing resample and the constant-width inflation:
generate → constant-spacing resample → RELAXATION → inflate
A raw constant-spacing centerline is not yet inflatable. The centerline generators produce closed loops that self-approach (two distant arcs of the loop passing close in the plane) and that contain under-radius corners tighter than the road half-width. If such a centerline were inflated directly into a constant-width band, the band would overlap itself or pinch to zero at the tight corner, and the track would fail the downstream thickness gate. Relaxation reshapes the bead chain — opening self-approaches and rounding tight corners — so that a constant-width inflation of it succeeds.
Top row: raw constant-spacing centerlines (relax_enable=False), drawn as
centerlines only. Bottom row: the same seeds after relaxation and inflation into a
constant-width band. Relaxation is what makes the bottom row inflatable.¶
Why it is needed¶
Validity is decided after relaxation, by the inflation stage’s thickness gate. A track is thickness-valid when
where \(r_{\min}\) is the smallest local (Menger) radius of curvature,
\(\text{sep}_{\min}\) is the smallest non-adjacent bead-pair distance, and
relax_tol = 0.02. The half of sep_min is the load-bearing quantity: two arcs a
distance \(d\) apart can only host two half-width roads without overlap if
\(d \ge 2\cdot\text{half\_width}\). A raw centerline routinely violates both terms;
relaxation exists to satisfy them with margin to spare.
Because the gate is applied post-relax, relaxation is the single stage that determines end-to-end yield. At the library defaults the current solver reaches ~0.999 relaxed-valid yield.
What changed¶
The relaxation solve was recently accelerated. The default is now 50
Chebyshev-accelerated Jacobi sweeps (relax_iters=50, relax_accel="chebyshev"),
replacing the previous 150 plain-Jacobi sweeps. The accelerated solve reaches
equal-or-higher yield and equal-or-better quality in a third of the sweeps, and roughly
half the wall-clock — see Track relaxation — convergence and tuning for the measured tables. A short
post-solve smoothing tail (shrink-free Taubin passes plus spacing polish,
relax_smooth_passes / relax_smooth_spacing_iters) then strips the fine curvature
noise the constraints’ deadband leaves behind, cutting curvature-difference RMS by ~a
third with no validity loss.
The four pages¶
Track relaxation — setup and constraints — the solver setup (rest length, separation band, targets) and the three per-sweep corrections (separation, spacing, bending), plus the gate coupling: why the separation target deliberately overshoots the validity gate’s floor, and why weakening that overshoot was measured to collapse yield.
Track relaxation — the solver — how the solve executes: Jacobi double-buffering, the dense and cached separation execution modes, and the Chebyshev semi-iterative acceleration that is the substance of the recent update.
Track relaxation — convergence and tuning — convergence behaviour and tuning: why plain Jacobi needed ~150 sweeps, what acceleration buys (with measured yield/quality/timing tables), the
relax_sep_everyshort-run pitfall, tuning guidance, and a list of measured dead ends.
The implementation lives in track_gen/_src/warp_relax.py (setup + solve); the
pipeline glue and post-relax validity gate live in track_gen/_src/warp_pipeline.py
(_run_pipeline, _validity_k, _thickness_func, RelaxScratch). It is the
only production relax backend used by TrackGenerator (relax_solver="xpbd").