Naming conventions#
How things are named in T3Toolbox, so you can predict a name you have never seen — and find the
function you want from the name alone. The conventions exist to help the reader; where a
convention would have hurt more than helped, we broke it deliberately, and the exceptions are
cataloged at the end. (The rules contributors must follow when naming new things — including
which of these conventions are rename-invariant — are in
contributor/naming_rules.md.)
The two layers#
Frontend (
t3toolbox/*.py): thin object-oriented classes over frozen dataclasses. Methods carry no family prefix — the class is the namespace (x.probe(...),v.apply_derivatives(...)).Backend (
t3toolbox/backend/*.py): stateless functions on raw.datatuples / supercores. Public backend functions carry a family prefix naming their primary operand’s representation. Backend users import submodules explicitly (from t3toolbox.backend import probing); nothing is re-exported flat.
The family prefix grammar#
[w][u]<family>_ — modifiers stack in order weighted → uniform → family.
Prefix |
Operand |
Example |
|---|---|---|
|
ragged Tucker tensor train data |
|
|
uniform T3 data |
|
|
tangent vector: a |
|
|
uniform tangent vector |
|
|
frame / variations data as indexed pieces (coarse: frame-only and variations-only ops share it) |
|
|
uniform frame / variations |
|
|
a bare TT core chain (no Tucker, no masks) |
|
|
a dense (full) tensor operand |
|
(none) |
representation-agnostic infrastructure |
|
w (weighted) exists only on t3/tv; the weighted layer is parked pending a redesign.
The founding disambiguation example: fv_to_t3(index, frame, variations) materializes ONE
indexed term of the pair, while tv_to_t3(frame, variations) is the efficient SUM of all
terms — distinct math on the same data, told apart by the prefix alone.
Polymorphism: the ragged name is the polymorphic name#
Many backend ops accept ragged or uniform operands and dispatch internally
(is_ndarray on the input — a tuple of cores is ragged, one stacked supercore is uniform).
Such a function lives unadorned in the base family; there is no separate u-twin. The
tt_ chain family is fully polymorphic (tt_reverse, tt_squash_tails,
tt_left_orthogonalize serve both layers — utt_ is reserved but has no members); t3_apply
and t3_entries likewise take either representation. A u-prefix therefore signals
uniform-specific logic (masks, supercore packing), not merely “works on uniform”.
The module map (family × operation kind)#
Backend modules tile a matrix of (family) × (operation kind: constructors · conversions · operations · linalg · orthogonalization · svd · sampling · masking):
constructors |
conversions |
operations |
linalg |
orthogonalization |
svd |
masking |
|
|---|---|---|---|---|---|---|---|
t3 |
|
|
|
|
|
|
— |
ut3 |
|
|
|
|
|
|
|
tv / utv |
— |
— |
|
— |
— |
— |
— |
fv / ufv |
— |
|
|
— |
— |
— |
|
tt |
— |
— |
|
— |
|
— |
— |
Outside the matrix: the unprefixed infrastructure (common, contractions, stacking,
linalg, ranks) and the fitting/optimization stack (fitting, optimizers,
uniform_fitting), which is organized by role, not by operand family.
Sampling is the deliberate exception — the ragged sampling modules are grouped by
sampling type, not by family: apply.py, entries.py, probing.py each hold their type’s
t3 + tv + dense ops, transposes, and frame sweeps, plus one sampling_derivatives.py for all
the jet (derivative) machinery. Why: for one sampling type the t3 evaluation, the tangent
𝒥/𝒥ᵀ, and the sweeps form a single connected algorithm (presented together in the paper), and
the containment probe ⊃ apply ⊃ entries gives the three files parallel structure —
apply.py and entries.py import the general machinery from probing.py, never the reverse.
The family axis survives inside each file through the function prefixes (t3_apply,
tv_apply, dense_apply_derivatives, …).
The uniform sampling modules (ut3_sampling.py, utv_sampling.py) are instead grouped by
object type — deliberately asymmetric. The uniform sampling functions are not independent
mathematical algorithms: each is a thin wrapper (mask once, pack, delegate to the shared
polymorphic ragged machinery, re-mask) whose few real operations are tied to its object’s
structure, not to the sampling math. Grouping wrappers by the object they wrap is what helps
the reader; the algorithm story lives in the ragged type files they delegate to.
Converters encode the operand type#
Within the uniform classes, t3-flavored names mean “the ragged layer”:
UniformTuckerTensorTrain.from_t3(x, ...) converts a ragged T3 to uniform, and
UT3Frame.from_t3frame / UT3Tangent.to_t3tangent are the cross-layer converters, while
from_ut3/to_ut3 are same-layer. On the ragged classes there is no such ambiguity, so
T3Frame.from_t3/to_t3 are same-layer. The names carry the types.
Some frontend modules also export a same-named function pair with the backend: e.g.
frame_variations_format.t3_orthogonal_representations (returns T3Frame/T3Variations
objects) wraps backend.fv_conversions.t3_orthogonal_representations (returns raw data), and
likewise ut3_orthogonal_representations. Same name = same operation; the module tells you
which layer you are holding.
Parameter names carry representation too#
x— ragged T3 data;data— uniform UT3 data (supercores + shape + masks).xx— a collection of ragged objects;uxx— a collection of uniform objects.frame/variations(frontend objects) vsframe_data/variations_data(raw tuples).ww— probe/apply vectors;pp— perturbation vectors;index— entry indices.Batch blocks are single capital letters —
C(frame stack),W(probe stack),K(tangent stack), frame-inner orderW+K+C— and body locals encode their axis layout as a name suffix (mu_WCa,C_aib); seedocs/batching_and_stacking.mdanddocs/contributor/signature_style.md.
Semantic markers (load-bearing — you can trust them)#
corewise— coordinate-space math on the core arrays, never Hilbert–Schmidt / tensor space.sum_stack(true tensor sum; rank-growing) vssum_stack_corewise(elementwise core sum; rank-preserving, NOT the tensor sum);corewise_dot/utv_corewise_inner(coordinate metric) vst3_inner_product/MANIFOLD.inner(HS);t3_corewise_randn(iid core entries) vsMANIFOLD.randn(tangent-space Gaussian).numerically_— an SVD-grade numerical check, vs bare structural integer arithmetic (has_minimal_ranksvshas_numerically_minimal_ranks)._trssuffix (sampling-derivative jets) — the dense binomial-tensor implementation, kept as a reference twin of the standard recurrence/scan form that owns the bare name.compute_mu_jets(the standard, memory-lean, call-site-wired form) vscompute_mu_jets_trs(contracts thetrsbinomial tensor as a dense einsum operand: numerically equal to tolerance, higher peak memory, occasionally faster in tiny/memory-abundant regimes). The bare name is always the recommended default;_trsis opt-in. (trs= the binomial convolution tensortrs[t,r,s] = C(t,r)·[r+s=t], the module’s own term.)Capability-honest tails —
ut3_full_sum(all modes only, vs raggedt3_sum(axis=...)),ut3_norm_orthogonalized(always orthogonalizes). The uniform twin is not renamed to match the ragged name when it genuinely supports less.
Why “frame”, not “basis”#
The orthogonal representation of a tangent space (T3Frame, the fv_/ufv_ families) is
deliberately not called a basis. “Basis” implies minimality — a linearly independent spanning
set — and the orthogonal representation does not require minimal ranks: at a non-minimal-rank
point it is overcomplete, and the library supports that on purpose (see the numerical-contracts
material: minimal rank is a precondition for essentially nothing). “Frame” is the idiomatic term in
manifold optimization for exactly this kind of possibly-redundant orthogonal spanning system. (The
T4S paper names the object neither way, so there is no paper conflict to worry about.)
Cataloged exceptions (deliberate)#
Sampling modules are grouped by type, not family (above).
Brand names:
t3svd,t3m(and compounds liket3m_swap) are algorithm names, exempt from prefix-position rules.Chain helpers keep descriptive names:
tt_zipper_left_to_right,tucker_change_core_shapes, and the uniform supercore-pair workers (up_orthogonalize_tt_supercores,ut3svd_supercores) — the name states the operand better than a bare family prefix would.Fitting/optimization is role-named:
uniform_minimal,uniform_least_squares_problem, theuniform_*_kindseam builders,pack_sample/pack_data, and the packedness utilities (is_packed,pack_if_ragged) are fitting infrastructure, not family data ops.MANIFOLD/COREWISEare the frontend geometry singletons; the backendGeometryOpssingletons areMANIFOLD_OPS/COREWISE_OPS.Frontend methods stay unprefixed even when a same-named backend function exists at a different level (e.g. the method
x.rank_adjustment_sweep()vs backendt3_rank_adjustment_sweep); the class namespace disambiguates.Frontend free functions DO carry the family prefix — they have no class namespace, so the prefix does that job instead. It is the same rule as the bullet above, not an exception to it: whatever disambiguates, disambiguates. Examples:
t3_orthogonal_representations/ut3_orthogonal_representations, and the weighted layer’st3_absorb_weights/ut3_absorb_weights/fv_absorb_weights/ufv_absorb_weights(+t3_/ut3_weighted_norm,_weighted_inner).Sharing a name with the backend twin is fine, and intended —
t3_orthogonal_representationsexists in bothframe_variations_format(objects) andbackend/fv_conversions(raw.data). Same operation, two levels; the module namespace tells them apart, and the backend is never re-exported flat, so they never meet.The prefix is what makes the root export possible. Ragged/uniform × tensor/tangent gives four distinct “absorb the weights” operations; unprefixed they would all be
absorb_weightsand could not coexist int3toolbox/__init__.py(the second import would silently shadow the first). The weighted free functions were briefly unprefixed and hit exactly this; prefixing them was the fix (2026-07-15). The classes never had the problem —T3Weights/UT3Weights/T3FrameWeights/UT3FrameWeightsare already distinct.
require_concrete_masksis a jit guard (infrastructure), unprefixed by design — and lives inbackend/common.pyfor the same reason, beside theValueHashedMasksmixin andprefix_mask: those three are the uniform mask-representation contract (concrete host arrays · value-based hash/eq · the canonical prefix form), which every uniform object’s masks obey — plain, frame, variations, and weights. They are not part of any one object’sut3_*/ufv_*family.
Intended asymmetries (uniform vs ragged)#
Where a uniform class or method offers less surface than its ragged twin, the gap is a design decision, not an unfinished port — do not read these as TODOs:
UT3Tangenthas nosave/load/to_vector/from_vector(all four exist onT3Tangent). A varying-rank uniform stack has no single flat-vector signature to reconstruct from; jax-native optimization works on the pytree directly, and flat-vector interop (e.g. scipy) routes through the ragged layer.UniformTuckerTensorTrain.sum_stack()takes noaxis(ragged:sum_stack(axis=None)).UniformTuckerTensorTrain.to_dense()takes nosquash_tails(ragged has it).UniformTuckerTensorTrain.t3svd(...)has nortol/atol— uniform truncation is structural (max-rank masks), never numerical; per-element numerical truncation could give different ranks per stack element, which the uniform shape cannot represent (seeuniform_equivalence_contract.md).
The capability-honest-tails rule (above) is the same principle at the function level.