S2 — Numerical-contract catalog (precondition vs caveat sweep)#
The decision record that designed safe mode, preserved as approved (2026-06-19). The
current-state statement of these contracts — present tense, migration arrows resolved — is the
user page ../numerical_contracts.md.
The make-or-break input to dev/archive/safe_unsafe_mode_plan.md §5. Sweep of the
verified modules (tucker_tensor_train, frame_variations_format, manifold, corewise, fitting +
backend, probing) classifying each op’s numerical assumptions as an enforceable precondition or a
non-enforced caveat.
APPROVED by Nick, 2026-06-19, with these decisions:
retractprecondition = ORTH only (not GAUGE — retract is gauge-invariant). ✓
MANIFOLD.innerchecks all of SF + ORTH + GAUGE (+ structural-minimal, below), the frame ones cached on the frozen objects. ✓Frontend-only enforcement for now; a backend mirror is deferred and needs careful discussion (current lean: no backend numerical checks). ✓
No reclassifications. ✓
Minimal ranks: do NOT retire — keep this catalog as the answer to the long-standing “which ops need minimal ranks” TBD (a reference that does not exist in the literature). In safe mode, check ranks are structurally minimal (
has_minimal_ranks, cheap rank arithmetic); never run the numerical (SVD) check; document the requirement on each op. See the revised “Minimal ranks” section. The structurally-but-not-numerically-minimal gap is an adversarial edge case (failure mode: NaN / wrong result) we accept.
The classification rule#
Precondition — the op is undefined or numerically wrong without it. Safe mode checks it (and raises); unsafe mode / under-jit skips it.
Caveat — the op is valid and correct as computed; the property only governs what the result means (e.g. “this equals Hilbert–Schmidt”). Never enforced — it would reject legitimate use.
The classic trap: orthogonality/gaugedness for inner/norm is a caveat (they’re valid on any frame;
those properties only make them equal HS), so it moves to the geometry’s inner/norm, not the raw op.
The numerical properties (and their checkers / cost)#
property |
meaning |
checker (exists) |
cost |
notes |
|---|---|---|---|---|
same-frame |
two tangents share one frame (= same tangent space) |
new |
O(1) common case |
|
orthogonal |
the frame is orthonormal (U/O/L/R conditions) |
|
contractions |
a |
gauged |
variations satisfy the gauge conditions (48)–(49) |
|
contractions |
per (frame,variations) pair; cacheable. |
minimal rank |
structurally-and-numerically minimal ranks |
|
SVD (numerical) |
numerical check intentionally skipped (§ “Minimal ranks”). |
Checkers are per-stack-element. Every checker/residual above returns one verdict per stack element (shape
stack_shape, scalar when unstacked) — seedocs/batching_and_stacking.md§9. So a safe-mode precondition reduces with.all()at the call site (safety.require(frame.is_orthogonal(atol).all(), …)): it requires all stack elements to pass;safety.requireitself stays a scalar gate. (The structuralhas_minimal_ranksis the one scalar-in-ragged exception — see that §9 note.)
Master table (by surface)#
SF = same-frame, ORTH = orthogonal frame, GAUGE = variations gauged. “—” = no numerical precondition
(structural checks like shapes/ranks/check_fv_pair still apply, always, in both modes).
T3Tangent (after the §S3 rename)#
op |
precondition (checked in safe mode) |
caveat (never checked) |
note |
|---|---|---|---|
|
SF |
— |
today an |
|
— |
— |
|
|
SF |
“= HS iff ORTH+GAUGE+minimal” |
renamed from |
|
— (unary) |
“= HS iff ORTH+GAUGE+minimal” |
renamed from |
|
|
— |
|
|
— |
— |
realization is gauge-invariant, valid on any frame |
|
— |
— |
bare 𝒥/𝒥ᵀ — gauge-invariant, any frame; the Riemannian |
|
— |
— |
structural / constructors |
|
SF (all leaves) |
— |
today identity |
|
— |
— |
|
|
— |
— |
checkers — they are the checks; keep non-enforcing |
Geometries (the semantic inner/norm live here — Nick’s refinement)#
op |
precondition |
caveat |
note |
|---|---|---|---|
|
SF + ORTH + GAUGE(both) |
minimal (for exact HS) |
the HS inner product; checks ORTH+GAUGE, can’t cheaply check minimal |
|
ORTH + GAUGE |
minimal |
unary (no SF) |
|
SF |
— |
Euclidean; no ORTH/GAUGE (the corewise frame is non-orthonormal by design) |
|
— |
— |
Euclidean, unary |
|
ORTH |
minimal (for oblique’s HS-matching purpose) |
the orthogonal-gauge projection needs an orthonormal frame to be the HS-orthogonal one |
|
ORTH |
minimal (rank preservation) |
precondition implied, not currently documented — confirm |
|
ORTH |
— |
docstring already states “Requires orthogonal; minimal NOT required” ✓ |
|
ORTH (new_frame) |
— |
= |
|
ORTH (via |
minimal (for a true Gaussian on |
docstring already careful: “for a non-orthogonal frame it is merely gauged” |
|
— |
— |
|
GaussNewtonModel (fitting)#
op |
precondition |
note |
|---|---|---|
|
SF ( |
today |
TuckerTensorTrain, corewise, probing — precondition-free#
surface |
precondition |
why |
|---|---|---|
|
— |
exact HS for any cores ( |
|
— |
exact for any cores; only structural shape/rank checks |
|
— |
the raw coordinate (“basic types”) layer; structural shape-match only |
|
— |
bare 𝒥/𝒥ᵀ contractions; gauge/frame-agnostic |
|
— (structural) |
|
The blurs this fixes (current code conflations)#
The same-frame guard is labelled “structural” but is numerical (
fitting._require_at_framedocstring “Structural guard”;T3Tangent._check_same_tangent_spaceviais). The root cause. →same_frame(identity fast-path +frames_equal), safe-mode, eager-only. This is what unblocks frame-as-leaf.T3Tangent.inner/normfold the HS caveat into the op (.. warning:: equals HS only when orthogonal and gauged). → split:MANIFOLD.inner/norm(HS, checks ORTH+GAUGE+SF) vscorewise_inner/corewise_norm(raw, checks SF). The op no longer pretends to be HS.retract/MANIFOLD.projectORTH precondition is silent (not in the docstrings). → state + check ORTH. (Confirmretractneeds only ORTH, not GAUGE — gauge is a redundancy retract is invariant to.)“some tangent ops only correct when minimal ranks — which exactly is TBD” (
manifold.py/bvf). → resolved as a caveat everywhere (see below); the TBD note can be replaced with the table above.
Minimal ranks — the resolution of the long-standing TBD (EMPIRICALLY verified — this is the reference)#
Minimal rank splits into structural (has_minimal_ranks — ranks equal the structural minimum; cheap
integer arithmetic) and numerical (has_numerically_minimal_ranks — no stored rank numerically
redundant; needs an SVD for a tensor, free for an orthonormal frame). An experiment settled which ops
actually need it (tests/-style script run 2026-06-19; each op compared on a minimal vs a non-minimal
orthogonal frame against the dense oracle):
op |
needs minimal? |
evidence |
|---|---|---|
|
NO |
|
|
NO |
the formula matches the true SVD rank (103) on the non-minimal frame. Correct for any ranks. |
|
soft caveat only |
still a valid first-order retraction on a non-minimal frame (rel.err 6e-6); it just drops the numerically-redundant rank (tucker 4→3) — desirable cleanup, not an error. Minimal buys only strict rank preservation. |
|
NO (correctness) |
gauged direction is valid on any orthonormal frame; “true Gaussian / HS-matching” is on whatever the stored-rank tangent space is, which is correct. |
|
NO |
orthogonal suffices (already known). |
Verdict: minimal rank is NOT a correctness precondition for any verified op. So we do not wire it as
a checked precondition — a structural-minimal check would raise on legitimate rank-redundant bases for
no correctness reason. (Also corrects an earlier claim: t3_orthogonal_representations does not
guarantee structural minimality — e.g. randn((10,11,12),(4,5,4),(1,2,3,1)) → has_minimal_ranks=False.)
Minimal rank lives on as a caveat (retract strict rank-preservation) and as diagnostic checkers
(built, not wired into any op): structural has_minimal_ranks (existing); numerical
TuckerTensorTrain.has_numerically_minimal_ranks(rtol) (structural-first → t3svd compare) and
T3Frame.has_numerically_minimal_ranks(atol) (orthogonal + structurally-minimal ⟹ numerically-minimal, no
SVD; non-orthogonal frames return False — the SVD path for them is deferred, “maybe we don’t need it”).
Super-safe mode was considered and dropped (no op needs it; it would be a ~no-op for orthogonal
frames). Naming convention: bare minimal_ranks = structural; numerically_minimal = numerical.
Implementation notes (for S3–S5, not decisions here)#
same_frame(b1, b2)=b1 is b2 or safety.frames_equal_or_skip(b1.data, b2.data)— theisfast-path keeps the common eager case O(1); the value compare only runs (and only matters) when objects differ but values match (jit round-trip) or genuinely differ. The tolerance is jax-aware (rtol_jaxif any input is a jax array, elsertol_numpy); under a trace it skips (returns pass). Mechanism int3toolbox/safety.py(S1, done).Where checks live (razor): to serve raw-
.datausers, the ORTH/GAUGE checks belong at the backend functions that consume raw data (tv_operations.*, which already haveis_orthogonal-style residual helpers), with the frontend methods inheriting them.same_frameis a frontend concept (it needs the twoT3Frameobjects); the backend’s analogue is “the caller passed one shared frame”, so the same-frame check is naturally frontend-only.Cost mitigation (done in S5): cache the atol-independent residual, not the atol-bool —
T3Frame.orthogonality_residualandT3Tangent.gauge_residualare@cached_propertys, andis_orthogonal(atol)/is_gauged(atol)compare against them. A fixed frame/tangent in an inner loop is contracted once; the atol stays a free parameter.Trace detection (done in S5): the eager-only skip must fire even when the checked operand is a closed-over concrete array (not a tracer) while globally inside a trace — a jnp op then still yields a constant tracer.
safety.is_tracingdetects the global trace state (jax.core.trace_state_clean, with a committed-array-probe fallback), not just whether the passed arrays are tracers.
Sign-off questions for Nick#
retractprecondition = ORTH only (not GAUGE)? My read: yes — retract is gauge-invariant; gauge only relabels the variations, not the represented step. Confirm.MANIFOLD.innershould check both operands GAUGE + the frame ORTH (3 checks) — accept the cost in safe mode (mitigated by caching), or check only SF + GAUGE and treat ORTH as a frame-construction invariant (sinceMANIFOLD.frameguarantees it)? Leaning: check all three for honesty, cache the frame ones.Backend-level enforcement for raw-
.datausers (ORTH/GAUGE checks intv_operations), or frontend-only for now? Leaning: frontend-first (S3–S5), backend mirror later.Any op above you’d reclassify (precondition ⇄ caveat)?