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 .data tuples / 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

t3_

ragged Tucker tensor train data (tucker_cores, tt_cores)

t3_probe, t3_to_dense, t3_inner_product

ut3_

uniform T3 data (tucker_supercore, tt_supercore, shape, masks)

ut3_probe, ut3_squash_tails

tv_

tangent vector: a (frame, variations) pair treated as the sum of its terms

tv_apply, tv_retract, tv_to_t3

utv_

uniform tangent vector

utv_apply, utv_retract, utv_to_ut3

fv_

frame / variations data as indexed pieces (coarse: frame-only and variations-only ops share it)

fv_to_t3 (single indexed term), fv_variations_zeros, fv_frame_reverse

ufv_

uniform frame / variations

ufv_apply_frame_masks, ufv_variations_reverse

tt_

a bare TT core chain (no Tucker, no masks)

tt_reverse, tt_squash_tails, tt_left_orthogonalize

dense_

a dense (full) tensor operand

dense_probe, dense_t3svd

(none)

representation-agnostic infrastructure

common, contractions, stacking, linalg, ranks

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

t3_constructors

t3_conversions

t3_operations

t3_linalg

t3_orthogonalization

t3_svd

ut3

ut3_constructors

ut3_conversions

ut3_operations

ut3_linalg

ut3_orthogonalization

ut3_svd

ut3_masking

tv / utv

tv_operations / utv_operations

fv / ufv

fv_conversions / ufv_conversions

fv_operations / ufv_operations

ufv_masking

tt

tt_operations

tt_orthogonalization

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) vs frame_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 order W+K+C — and body locals encode their axis layout as a name suffix (mu_WCa, C_aib); see docs/batching_and_stacking.md and docs/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) vs sum_stack_corewise (elementwise core sum; rank-preserving, NOT the tensor sum); corewise_dot / utv_corewise_inner (coordinate metric) vs t3_inner_product / MANIFOLD.inner (HS); t3_corewise_randn (iid core entries) vs MANIFOLD.randn (tangent-space Gaussian).

  • numerically_ — an SVD-grade numerical check, vs bare structural integer arithmetic (has_minimal_ranks vs has_numerically_minimal_ranks).

  • _trs suffix (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) vs compute_mu_jets_trs (contracts the trs binomial 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; _trs is opt-in. (trs = the binomial convolution tensor trs[t,r,s] = C(t,r)·[r+s=t], the module’s own term.)

  • Capability-honest tailsut3_full_sum (all modes only, vs ragged t3_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 like t3m_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, the uniform_*_kind seam builders, pack_sample/pack_data, and the packedness utilities (is_packed, pack_if_ragged) are fitting infrastructure, not family data ops.

  • MANIFOLD / COREWISE are the frontend geometry singletons; the backend GeometryOps singletons are MANIFOLD_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 backend t3_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’s t3_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 intendedt3_orthogonal_representations exists in both frame_variations_format (objects) and backend/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_weights and could not coexist in t3toolbox/__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 / UT3FrameWeights are already distinct.

  • require_concrete_masks is a jit guard (infrastructure), unprefixed by design — and lives in backend/common.py for the same reason, beside the ValueHashedMasks mixin and prefix_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’s ut3_*/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:

  • UT3Tangent has no save/load/to_vector/from_vector (all four exist on T3Tangent). 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 no axis (ragged: sum_stack(axis=None)).

  • UniformTuckerTensorTrain.to_dense() takes no squash_tails (ragged has it).

  • UniformTuckerTensorTrain.t3svd(...) has no rtol/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 (see uniform_equivalence_contract.md).

The capability-honest-tails rule (above) is the same principle at the function level.