Deferred and rejected — the ledger#
Deliberately-not-built features and considered-and-rejected designs, each with its why and a source pointer, so settled calls are not re-litigated and deferred ideas are not lost. (Entries that already have their own note or live in another internals doc appear here as one-line cross-references.)
Rejected#
Truncation kwargs on dense tangent projection (
project_dense_onto_tangent). Rejected: with the library’s full-SVDtruncated_svd, the t3svd-then-project path’s first unfolding SVD costsO(Nᵈ·N₀)regardless of any tolerance — truncation only shrinks steps that are not the bottleneck — while the contraction method’s dominant step isO(Nᵈ·n)withn ≤ N₀the Tucker rank. Contraction therefore dominates everywhere; the literature agrees (projection is contractions; the SVD belongs to retraction/rounding). The't3svd'method is kept solely as an independent cross-check oracle (it is a second implementation of the same map, which is what verified the contraction code). Source: the t3m-swap planning session,dev/archive/t3m-swap-planning_2026-06-13.md(“keep both methods, contraction as default, skip the truncation options”).A kwarg-unified derivative interface (
probe(..., order=)instead of separate*_derivativesmethods). Rejected (locked decision): the derivative output gains a leadingorderaxis, so a unified method would have return-rank polymorphism — output rank depending on whether a kwarg was passed — which fights the shape-comment-as-contract style, makes generic consumption harder, and is worse still for the transposes (the residual input rank becomes context-dependent). A named*_derivativesmethod also self-documents an otherwise-buried advanced feature. Source:dev/archive/derivatives_mirror_plan.md§”Frontend design decision”.Any global / ambient jit toggle (context manager, module flag, contextvar). Rejected: the bar for global toggles is very high (
safety.py’s contextvar is the one sanctioned global, justified as correctness-neutral); the jit choice must be per-function, at the call site — which is what theuse_jitper-optimizer parameters and the close-over recipe deliver. Source:dev/archive/jit_oo_handoff.md.
Deferred (would be built if the need materializes)#
Per-axis uniform
sum_stack(axis=)(review H2-7; ruled defer + document, 2026-08-24). Raggedsum_stacktakes an axis subset; the uniform twin sums the whole stack only. The gap is the mask algebra: a subset sum multiplies ranks by the summed sizes per element while keeping the other stack axes’ rank variation, so the S-fold concat/gappy-mask machinery of+must be applied along a kept/summed axis split. Reachable from the existing primitives; meanwhile the ragged detour (to_t3()→sum_stack(axis)→from_t3) or unstack/restack covers it. Thesum_stackdocstring states the asymmetry.A guard that a sampling kind and a geometry were built at the same rank. Both now carry
shapeandmasksas comparable fields, but nothing compares them. Pairing a kind built at one rank with a geometry at another — reachable when the two ranks have identical padded shapes, so no shape error catches it — runs to completion and produces a wrong objective.Problem.__post_init__could compare them when both are present.~~
d = 1on the uniform layer.~~ Resolved 2026-08-22. The cause was notut3svd_supercoresbut_tt_squash_tails_uniform, which concatenated[first, middle, last]and so duplicated the single core; every uniform op that squashes (+,-,squash_tails,sum_stack,norm,t3svd, the frame) failed downstream.d = 1is now a supported degenerate case on both layers: a one-mode T3 is a vector in a rank-nsubspace, rank is not meaningful, and every op reduces to the vector case (tests/test_uniform_tucker_tensor_train.py::TestUniformGarbageAndDegenerate).Stacked optimization. A stacked point carries a batch of base points
C, so every objective is an array of shapeC– but the optimizers’ convergence tests, Armijo line searches and plateau detection all reduce it with a Pythonfloat(). All four now raiseNotImplementedErrorat the entry rather than failing from inside the loop. The Gauss-Newton model supports stacked points and returns a shape-Cobjective, so rolling your own loop works today; making the optimizers vectorize overC(and deciding what a per-element convergence test means when elements converge at different rates) is the real work. Low priority. Regularizing a stacked point is a separate, sharper problem: the misfit keepsCwhile every regularizer scalar collapses it, so it raises regardless of the optimizer (fitting_and_optimization.md§4.10).Consolidating the sharing-partition normalization at its remaining call sites.
None-or- all-singletons → “skip the shared path” is open-coded at seven sites inbackend/ranks.py,backend/t3_svd.pyandbackend/ut3_svd.py, each normalizing to aNonesentinel.sharing.canonical_groupsis now the named form (it normalizes to(), which a geometry can store as a field and compare by value). The seven were verified duplicative, not wrong; converting them means touching the SVD layer, so it was left out of the optimization-layer work.A shared base for the repeated uniform-geometry methods.
_variations,n_stack,base_pointandinnerare identical acrossUniformManifoldGeometryOpsandUniformCorewiseGeometryOps(~20 lines). A mixin would remove the repetition; left alone because dataclass inheritance with defaulted fields is finicky enough that the duplication is currently the clearer of the two.Jitting the outer Newton loop. Per iteration the host does roughly a dozen device→host syncs, plus one per line-search backtrack (up to 40). Noise on CPU, not on a GPU. It is downstream of the pytree
LocalModelthat now exists, so the prerequisite is met; what it fights is thecallbackdiagnostic display, which reads the concrete residual host-side (jax.debug.callbackis the likely route). Its own decision, deliberately kept out of the jit-boundary work.The bidiagonal-
trsjet optimization — deferred, then BUILT; now the standard form. The pushthrough/assembly contractions convolve a full-order jet with the input/perturbation jet, which is capped at 2 orders (s ∈ {0,1}), sotrs[t,r,s]is nonzero only atr ∈ {t, t−1}— bidiagonal,O(K)where the dense(K+1)×(K+1)einsum isO(K²). Deferred at design time (training cost secondary,K ≤ dsmall); the measurement that motivated it (W=300, ranks 4): derivative apply forward ~32× the plain op at order 4, transpose ~78×. It was built afterwards as the recurrence/scan forms inbackend/sampling_derivatives.py—compute_mu_jetsis a fused two-term recurrence with notrsoperand at all, and the genuinely-full convolutions (eta/deta/tilde) scan the summed order axis instead. Those are the defaults at every call site; the dense binomial forms survive as the*_trsreference twins (compute_mu_jets_trs,compute_eta_jets_trs, …), the oracle intests/test_jet_recurrence.py. Source:dev/archive/derivatives_mirror_plan.md§”Future-work / perf optimization”.Structured tangent stacks (
K) — open idea (Nick, 2026-06-12). When theKtangent vectors carry special structure — e.g. an orthonormal block within one tangent spaceT_x M— can𝒥⁽ˢ⁾be applied more cheaply than the general 3-group sweep? Plausible angles: shared sub-contractions across the block, a factored form of the perturbation edge variables, work that cancels under orthonormality. Not pursued; the general (arbitrary-K) path stays the default regardless. Source:dev/archive/probing_section6_notes.md.Riemannian L-BFGS with vector transport — see the deferred list in
fitting_internals.md(which also records why plain L-BFGS is deliberately not a library optimizer: the scipy bridge is the intended pattern).The ambient derivative transpose — has its own analysis-and-deferral note:
ambient_derivative_transpose_note.md(intrinsically exponential-rank back-projection; no use case).numpy 2-operand einsum:
optimize=Falseis not BLAS (unresolved; low priority — Nick, 2026-07-15)._grouped_einsum’s docstring claims “2-operand contractions are already BLAS, so they pass straight through on both.” For numpy that is false: passing through meansoptimize=False, which runsc_einsum, never BLAS. Forcing the path (_pairwise_pathalready returns the correct('einsum_path', (0,1))for 2 operands) buys BLAS but pays a fixed ~20–25µs dispatch overhead, so there is a real crossover, measured on'WCi,Cio->WCo': 0.1× at W=1, 0.7× at W=32, 1.8× at W=64, 9.0× at W=2048. It loses up to 10× below W≈64 and wins up to 9× above. So the short-circuit’s effect is a defensible default for small contractions — a regime-dependent tradeoff, not a bug. The library’s operating point sits well above the crossover (Windexes the samples being fitted, so it is large by construction; the core dims are tied to it,r ≈ a ≈ i ≈ branging 1..sqrt(W)/2for probing, 1..cbrt(W)/2for entries/apply). But numpy is not the performance path — jax is, and jax’s einsum has good path selection — so the bar here is “not doing something dumb”, not “optimal”. Not free either: the BLAS path is not bit-identical (~1.7e-16, one ULP — different summation order). (Not to be confused with the MULTI-operand case, where numpy’s own optimizer picks a FLOP-tied non-BLAS path and_pairwise_pathis 22× faster on the 4-operandtrscontraction. That machinery is doing real work and must not be touched.) Source:dev/archive/contractions_sharding_plan.md§8.~~Backend
ut3_norm/ut3_innertwins~~ Built 2026-08-22 (with the review’s S11 fix: their jax derivative is the exact multilinear rule, not a derivative through the SVD, which was NaN on every padded train). The original note, for the record: (wanted eventually; low priority — Nick, 2026-07-15). The ragged backend has self-containedt3_norm(x, use_orthogonalization=True)/t3_inner_product(x, y, use_orthogonalization=True), which orthogonalize internally. The uniform backend exposes only the already-orthogonalized fast pathut3_norm_orthogonalized(plus a no-orthut3_inner_product), leaving the orthogonalize-then-norm composition to the frontend (UniformTuckerTensorTrain.norm/.inner). So a raw-.datauser must know to callut3_left_orthogonalize_tt_cores(ut3_down_orthogonalize_tucker_cores(x))first — two functions, in that order — which the backend/frontend razor says belongs in the backend (non-obvious, easy to get wrong, and the ragged twin already provides it). Unlike the gaps in../naming_conventions.md§”Intended asymmetries”, this one is not a design decision — it is an unfinished port. Noticed while writingut3_weighted_norm(uniform weighting S1), which had to reproduce the chain; it is parked in that module’s private_ut3_left_orthogonalizedhelper, so filling the gap means promoting that helper into realut3_norm/ut3_innerand having the frontend + weighted twins delegate. Source:weighted_internals.md(S1),backend/ut3_linalg.py.