Contractions

Tensor0 provides low-level axis APIs and higher-level label APIs for symmetry-aware contractions. The general axis and label APIs return a TensorMap; even a fully contracted network returns a rank-zero TensorMap, whose value is extracted explicitly with scalar(...). The tr(...) convenience operation instead returns a JAX scalar directly.

Visible axes are numbered as codomain axes followed by domain axes. Output arguments always specify both axis order and the resulting HomSpace partition.

Choosing an Operation

Operation Scope Index specification Result
tr Full square-map trace Implicit fused HomSpace blocks JAX scalar
tensortrace General single-tensor trace Axis tuples TensorMap
tensorcontract Two tensors Axis tuples and output references TensorMap
contract One or more tensors Named labels TensorMap
ncon One or more tensors Signed integer labels TensorMap

Use the axis APIs when code already knows exact axis positions. Use contract for readable hand-written tensor networks and ncon for conventional integer network descriptions or programmatically generated networks.

Binary Contraction

tensorcontract contracts paired axes and places every open axis through an explicit (operand, axis) reference. Operand 0 is the left tensor and operand 1 is the right tensor.

result = tensorcontract(
    left,
    right,
    axes=((1,), (0,)),
    output=(((0, 0),), ((1, 1),)),
)

This contracts left axis 1 with right axis 0, keeps left axis 0 in the result codomain, and keeps right axis 1 in the result domain. Contracted spaces must be dual-compatible. Every original axis must appear exactly once in either axes or output.

Pass conjugate=(True, False) or conjugate=(False, True) to contract an operand through its adjoint orientation. The flags are static operation metadata, not dynamic JAX array values.

Single-Tensor Trace

tensortrace pairs axes of one tensor and explicitly orders the remaining axes:

partial = tensortrace(
    tensor,
    axes=((1,), (3,)),
    output=((0,), (2,)),
)

For a full trace, both output groups are empty:

rank_zero = tensortrace(
    matrix,
    axes=((0,), (1,)),
    output=((), ()),
)
value = scalar(rank_zero)

Unlike tr(matrix), the general trace operation deliberately preserves the rank-zero TensorMap representation. conjugate=True traces the tensor after applying its adjoint orientation.

On the supported native CPU path, a long float32 or complex64 trace with too few independent outputs may use parallel partial sums. As with JAX/XLA floating reductions, changing the worker count can change the association and therefore the numerical error. For ill-conditioned or strongly cancelling sums, the relative difference need not be confined to the lowest bits. Signed zero, overflow classification, and NaN payload may also vary. Repeated execution with the same compiled program and worker count is reproducible; integer reductions retain exact results.

This numerical contract also applies to floating-point and complex reductions introduced by automatic differentiation, such as a scalar scale-factor cotangent. Elementwise maps and non-reduction cotangents keep their stricter value contract, while reduction results are compared numerically rather than by requiring one particular XLA association tree.

Named-Label Networks

idx binds comma-separated Python identifiers to visible axes in codomain- then-domain order:

result = contract(
    idx(left, "a,x"),
    idx(middle, "x,y"),
    idx(right, "y,b"),
    output=("a", "b"),
)

Open labels occur once and must appear exactly once in output. Contracted labels occur exactly twice and must join dual-compatible spaces. The output can be either a pair of comma-separated strings or one semicolon-separated string:

output=("a,c", "b,d")
output="a,c;b,d"

Tuple operands provide a compact alternative to idx:

result = contract(
    (left, "a,x"),
    (right, "x,b"),
    output="a;b",
)

A third tuple entry or idx(..., conjugate=True) requests conjugation.

Without order, named contraction folds operands from left to right. An exact tuple/list of contracted labels selects another deterministic association:

result = contract(
    idx(left, "a,x"),
    idx(middle, "x,y"),
    idx(right, "y,b"),
    output=("a", "b"),
    order=("y", "x"),
)

The earliest remaining label chooses a tensor pair. If that pair shares more than one label, all shared labels are contracted in the same binary operation. The order must contain every contracted label exactly once, including labels consumed by a self trace. Tensor products combine disconnected components after all contraction labels are consumed.

Integer-Label Networks

ncon uses positive labels for contractions and negative labels for open indices:

result = ncon(
    (left, middle, right),
    ((-1, 1), (1, 2), (2, -2)),
    order=(2, 1),
    output=((-1,), (-2,)),
)

Positive labels occur exactly twice; negative labels occur exactly once; zero is invalid. By default, positive labels are processed in increasing order. When output is omitted, negative labels retain their effective codomain/domain side and are ordered as -1, -2, and so on within each side.

Explicit output is always a (codomain_labels, domain_labels) pair because a TensorMap result must preserve the HomSpace partition. conjugate is an optional tuple/list containing one boolean per tensor.

Twist

twist(tensor, indices, inv=False) applies the ribbon twist to selected visible indices without changing the tensor's HomSpace:

twisted = twist(tensor, (0, 2))
restored = twist(twisted, (0, 2), inv=True)

Bosonic and otherwise trivial twists return the original tensor. Fermionic odd sectors acquire the corresponding sign. Current public contraction paths support symmetric braiding; anyonic and planar contraction are outside the v1 scope.

Index Flip

flip(tensor, indices, inv=False) changes the arrow presentation of selected visible indices and applies the corresponding Z-isomorphism weights. If two matching legs will be contracted, flipping both legs leaves the contraction unchanged. See the transform guide for its full inverse and HomSpace semantics.

JAX Boundary

TensorMap storage is the dynamic pytree leaf. HomSpace, labels, output groups, conjugation flags, and contraction order are static metadata used while JAX traces the Python operation.

@jax.jit
def network(left, middle, right):
    return contract(
        idx(left, "a,x"),
        idx(middle, "x,y"),
        idx(right, "y,b"),
        output=("a", "b"),
        order=("y", "x"),
    )

Changing only storage values reuses the same static structure. Changing a HomSpace or a closed-over label/output/order specification creates a different specialization. Differentiation flows through storage arrays; operation metadata is not differentiable.

Current Boundaries

Tensor0 does not currently provide automatic contraction-order optimization, hyperedges, repeated open labels, diagonal extraction through repeated output labels, ellipsis/broadcasting, mutable destinations, or a native JAX numerical kernel. Dense conversion remains a small correctness tool and is not used by the contraction implementation.