Track relaxation — setup and constraints¶
Relaxation is a PBD/XPBD-style position projection over the constant-spacing centerline
beads. It does not allocate dense [E,N,N] tensors, does not run an optimizer, and
does not store a per-constraint Lagrange-multiplier history. Instead, every fixed sweep
reads one complete position buffer, computes local position corrections, and writes a
second buffer (see Track relaxation — the solver). This page covers the per-env setup and the three
corrections each sweep applies, then the gate coupling that ties the separation
target to the downstream validity gate.
Setup¶
Setup happens once, before the sweeps, in band_l0_inplace (kernel _band_l0_k):
\(L_0[e] = \text{perimeter}(\text{center}[e]) / \text{count}[e]\), the rest edge length for the constant-spacing bead chain.
\(\text{band}[e] = \text{round}(2 \cdot \text{half\_width} / L_0[e])\), clamped to a minimum of 1, unless
config.relax_bandoverrides it. The band excludes immediate geometric neighbours from the separation scan, so the road does not try to push apart points that are adjacent along the same local segment. The identical_separation_band@wp.funcis shared with the pipeline’s_validity_k, so the band definition lives in exactly one place.\(\text{target} = 2 \cdot \text{half\_width} \cdot (1 + \text{relax\_margin})\) is the non-local separation distance — a slightly inflated road diameter (default
relax_margin= 0.15).\(R_{\min} = \text{half\_width} \cdot (1 + \text{relax\_margin})\) is the local curvature-radius target used by the bending correction. Note \(\text{target} = 2\,R_{\min}\) exactly.
Per-sweep corrections¶
Each sweep applies three corrections per real bead i (kernels _step_kernel /
_step_cached_kernel):
Non-local separation¶
For every bead j whose circular index distance from i exceeds band[e], if
the current Euclidean distance is below target, bead i receives a push along
\(x_i - x_j\) of magnitude \(0.5\,(\text{target} - \text{dist})/\text{dist}\).
The pushes are averaged over all colliding candidates for that bead (Jacobi
averaging), then scaled by relax_sep_relax. This is the term that opens
self-approaches and makes enough room for a constant-width road.
Edge spacing¶
The two incident edges (i-1, i) and (i, i+1) are corrected toward the rest
length \(L_0[e]\) with the local formula
\(0.25\big(\tfrac{\ell_n - L_0}{\ell_n}d_n - \tfrac{\ell_p - L_0}{\ell_p}d_p\big)\),
scaled by relax_spc_relax, so the relaxed loop keeps near-constant bead spacing
instead of stretching into a few long chords.
Bending / radius guard¶
The local Menger curvature through (i-1, i, i+1) gives a radius estimate. If
\(\text{radius} < R_{\min}\), the bead is pushed toward the midpoint of its
neighbours by a scale of
\(\text{relax\_bend\_relax}\cdot (R_{\min} - \text{radius})/R_{\min}\), clamped to
1 so the bead never passes the chord midpoint in a single sweep (the flip clamp). This
removes jagged under-radius corners introduced by generation or by separation pushes.
The three corrections sum into a single per-bead step \(\Delta x = \text{sr}\cdot\text{sep} + \text{pr}\cdot\text{spc} + \text{bscale}\cdot\text{toward}\) written to the output buffer.
Count-aware kernels¶
The solver is count-aware. Kernels launch over the static stride N_max, but threads
with i >= count[e] copy the NaN-padded tail through unchanged. When every
count[e] == N_max, the same kernels reduce to the old fixed-N parity path.
The relax_* knobs¶
Knob |
Effect |
|---|---|
|
Scale factor applied to each separation push (damping). Default 1.0. |
|
Scale factor applied to edge-spacing corrections. Default 1.0. |
|
Scale factor applied to bending/radius-guard corrections. Default 1.5 (over-corrects slightly to remove sharp corners quickly). |
|
Inflation margin: |
|
Override the auto-computed separation exclusion band. Default |
|
Number of Jacobi sweeps. Default 50 (see Track relaxation — convergence and tuning). |
The separation execution knobs (relax_sep_every, relax_sep_cache_slots,
relax_sep_cache_skin) and the Chebyshev knobs (relax_accel, relax_cheby_rho,
relax_cheby_gamma, relax_cheby_start) are covered in Track relaxation — the solver.
Gate coupling — a load-bearing overshoot¶
The separation target is not the validity gate’s floor; it deliberately overshoots it, and that overshoot is load-bearing. Recent experiments established the relationship and, critically, that weakening it collapses yield.
At the library defaults (half_width = hw, relax_margin = 0.15,
relax_tol = 0.02):
the separation target is \(2\,\text{hw}\,(1.15) = 2.3\,\text{hw}\), and \(\text{target} = 2\,R_{\min}\) exactly;
the validity gate requires \(\tfrac{1}{2}\,\text{sep}_{\min} \ge (1 - \text{relax\_tol})\,\text{hw}\), i.e. \(\text{sep}_{\min} \ge 1.96\,\text{hw}\).
So the solver aims a bead pair across a legal minimum-radius hairpin at \(2.3\,\text{hw}\) while the gate would accept it at \(1.96\,\text{hw}\). Because \(\text{target} = 2\,R_{\min}\), a pair straddling a legal minimum-radius hairpin sits at zero separation slack relative to the target — the constraint pushes it exactly as hard as the geometry allows. At converged equilibrium the solver therefore holds tight hairpins a comfortable ~4–8% above the gate line, not on it.
That headroom is what carries yield. Two changes that look like harmless efficiency tweaks were both measured to be dead ends because they let near-band pairs settle onto the gate floor instead of above it:
Widening the exclusion band (
relax_band= auto + 1) removes the near-hairpin pairs from the separation scan entirely. Defaults-regime yield collapsed 0.99 → 0.74.Capping near-band pair targets at the chord-feasible distance (only pushing pairs apart to the distance a minimum-radius arc geometrically permits, rather than to the full
target) removes exactly the overshoot. Near-band pairs then settle onto the gate floor and float across it under float noise, again failing the gate.
Warning
These are recorded as measured dead ends, not options. Do not widen the exclusion
band and do not cap near-band pair targets at the chord-feasible distance: both let
near-band pairs settle onto the gate floor, and yield falls off a cliff. The overshoot
(target = 2*R_min = 2.3*hw at defaults) is deliberate margin — keep it.
Disabling relaxation¶
If relax_enable=False, _run_pipeline bypasses band_l0_inplace and
xpbd_solve_inplace and inflates the constant-spacing centerline directly (an
identity pass-through, useful for ablations). Otherwise the relaxed output is
re-uniformized by the inflation stage before frame, offset, validity, and arclength are
computed.