tenet.symmetry¶
The symmetry providers and their sectors: U(1), Z2, fermionic Z2, SU(2), SU(N) and products, plus the capability protocols and the coherence validators.
SU(N) lives in tenet.symmetry.sun and is not re-exported here: a provider carries its
own n, so there is no singleton to export. Its coefficients, and SU(2)'s, come from
racah-py, a core dependency, so import tenet.symmetry.sun works on a plain
pip install symtenet.
tenet.symmetry ¶
Symmetry providers and sector labels.
A provider answers every categorical question about one symmetry — fusion rules,
quantum dimensions, duality, F/R symbols, braiding — and a sector labels one irrep.
Trivial, Z2, fZ2,
U1 and SU2 are the shipped instances, each
beside its class and its *Sector type, and
ProductProvider composes two into one.
The capability protocol is the rest of the page: supports and
requires ask and demand, the *Data protocols name what a
provider may implement, and CapabilityError is the
refusal raised when one is missing.
Trivial
module-attribute
¶
Trivial = TrivialProvider()
Module-level singleton, used as GradedSpace(provider=Trivial, ...).
fZ2
module-attribute
¶
fZ2 = FZ2Provider()
Module-level singleton, used as GradedSpace(provider=fZ2, ...).
SU2
module-attribute
¶
SU2 = SU2Provider()
Module-level singleton, used as GradedSpace(provider=SU2, ...).
U1
module-attribute
¶
U1 = U1Provider()
Module-level singleton, used as GradedSpace(provider=U1, ...).
Z2
module-attribute
¶
Z2 = Z2Provider()
Module-level singleton, used as GradedSpace(provider=Z2, ...).
AssociatorData ¶
Bases: Protocol
Providers that supply the associator F, scalar-valued.
Scalar-valued is a multiplicity-free assumption: a provider with
n_symbol > 1 must raise rather than truncate a matrix-valued symbol, and
supply FMatrixData beside this protocol instead.
f_symbol ¶
[F^{abc}_d]_{e,f}; e is the inner line of ((ab)c), f of (a(bc)).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The three fused sectors, in order. |
required |
b
|
Sector
|
The three fused sectors, in order. |
required |
c
|
Sector
|
The three fused sectors, in order. |
required |
d
|
Sector
|
The total (coupled) sector. |
required |
e
|
Sector
|
The inner line of the |
required |
f
|
Sector
|
The inner line of the |
required |
Returns:
| Type | Description |
|---|---|
complex
|
The recoupling coefficient; exactly |
Raises:
| Type | Description |
|---|---|
ValueError
|
If any vertex of the labelling has |
BendingCoefficients ¶
Bases: Protocol
Providers that can move one line between the two trees of a block key.
bend_right ¶
bend_right(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
Move the LAST uncoupled line of output_tree onto the END of
input_tree, dualized.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
FusionBlockKey
|
The block key whose output tree loses its last line. |
required |
dual
|
bool
|
The moved leg's current dual flag; it selects the Frobenius-Schur factor. |
required |
Returns:
| Type | Description |
|---|---|
tuple of (FusionBlockKey, complex)
|
|
bend_left ¶
bend_left(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
The inverse direction: last line of input_tree onto output_tree.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
FusionBlockKey
|
The block key whose input tree loses its last line. |
required |
dual
|
bool
|
The moved leg's current dual flag. |
required |
Returns:
| Type | Description |
|---|---|
tuple of (FusionBlockKey, complex)
|
|
BMatrixData ¶
Bases: Protocol
Array-valued bend — DualityData's sibling
for N^c_ab > 1.
b_matrix ¶
B^{ab}_c, shape (N^c_ab, N^a_{c,dual(b)}).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The vertex's two fused sectors; |
required |
b
|
Sector
|
The vertex's two fused sectors; |
required |
c
|
Sector
|
The vertex's fusion channel. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape |
BraidingData ¶
Bases: Protocol
Providers that supply the braiding R, scalar-valued.
Notes
Having R does not make the braiding symmetric: transpose gates on
this protocol plus the symmetric-braiding property (R == R**-1), and a
chiral provider is refused rather than handed one of two inequivalent
braids. Multiplicity-bearing providers supply
RMatrixData beside this one.
r_symbol ¶
R^{ab}_c, the coefficient of braiding a past b inside c.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The two braided sectors, in order. |
required |
b
|
Sector
|
The two braided sectors, in order. |
required |
c
|
Sector
|
The fusion channel they are braided inside. |
required |
Returns:
| Type | Description |
|---|---|
complex
|
The braiding phase; unit modulus in a unitary gauge. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
BranchingRules ¶
Bases: Protocol
Providers that can be restricted to a smaller symmetry in the dense basis.
branch ¶
branch(
target: FusionRules, a: Sector
) -> tuple[Sector, ...]
The target sector of each of a's d_a dense basis vectors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
target
|
FusionRules
|
The smaller symmetry restricted to; must be abelian. |
required |
a
|
Sector
|
The sector of this provider being decomposed. |
required |
Returns:
| Type | Description |
|---|---|
tuple of Sector
|
One |
Raises:
| Type | Description |
|---|---|
CapabilityError
|
For a |
Notes
The order is the provider's own dense (magnetic) order — the same
order cgc and z_matrix use, so this composes with
to_dense's alpha * d_a + m layout without a second convention.
Every returned sector must satisfy target.irrep_dim(...) == 1: one
target label per basis vector is only well-defined when the target's
irreps are one-dimensional, i.e. when the target is abelian.
CapabilityError ¶
Bases: TypeError
Raised when a provider lacks a capability an operation requires.
ClebschGordanData ¶
Bases: Protocol
Providers that can supply explicit Clebsch-Gordan tensors.
Notes
A dense-basis capability: it exists exactly when the sectors are
representations of something. An anyonic provider (Fibonacci, Ising) has no
cgc and no irrep_dim at all, which is why quantum dimensions live on
QuantumDimensionData instead.
irrep_dim ¶
irrep_dim(a: Sector) -> int
The dense dimension d_a of the irrep labelled a.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The sector whose irrep dimension is asked. |
required |
Returns:
| Type | Description |
|---|---|
int
|
|
cgc ¶
Shape (d_a, d_b, d_c, N^c_ab); last axis is the multiplicity label mu.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The two fused sectors. |
required |
b
|
Sector
|
The two fused sectors. |
required |
c
|
Sector
|
The fusion channel selected. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
The Clebsch-Gordan tensor of |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
DaggerData ¶
Bases: Protocol
Marker for the dagger structure adjoint/conj rely on.
Every provider tenet ships is unitary in a real-or-unitary gauge, so the dagger needs no per-provider data yet and every provider satisfies this protocol. A provider with complex Clebsch-Gordan tensors or a non-unitary F/R gauge is the trigger for making it a real capability gate.
DualBasis ¶
Bases: Protocol
Providers whose V_a -> V_a^* isomorphism is available in the dense basis.
z_matrix ¶
z_matrix(a: Sector) -> ndarray
Z_a, shape (d_a, d_dual(a)), in the provider's own dense basis.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The sector whose |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
DualityData ¶
Bases: Protocol
Providers that supply rigidity: the dual label map and the bend B.
Notes
dual alone (the label map every provider carries) is not rigidity; the
B-symbol is what prices an evaluation/coevaluation bend. Multiplicity-bearing
providers supply BMatrixData beside this one.
dual ¶
b_symbol ¶
B^{ab}_c, the duality coefficient bending b out of a x b -> c.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The vertex's two fused sectors; |
required |
b
|
Sector
|
The vertex's two fused sectors; |
required |
c
|
Sector
|
The vertex's fusion channel. |
required |
Returns:
| Type | Description |
|---|---|
complex
|
The bend coefficient. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If any vertex of the labelling has |
FMatrixData ¶
Bases: Protocol
Array-valued associator for providers with N^c_ab > 1.
AssociatorData's array-valued sibling, a
capability beside it: the scalar symbol stays exactly as it is for every
multiplicity-free provider, and _artin_braid takes the array path only
when the provider also implements the array-valued protocols.
f_matrix ¶
[F^{abc}_d]_{e,f}, shape (N^e_ab, N^d_ec, N^f_bc, N^d_af).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The three fused sectors, in order. |
required |
b
|
Sector
|
The three fused sectors, in order. |
required |
c
|
Sector
|
The three fused sectors, in order. |
required |
d
|
Sector
|
The total (coupled) sector. |
required |
e
|
Sector
|
The inner line of the |
required |
f
|
Sector
|
The inner line of the |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape |
FSIndicatorData ¶
Bases: Protocol
Providers that supply the Frobenius-Schur indicator chi.
frobenius_schur ¶
frobenius_schur(a: Sector) -> complex
chi_a, the Frobenius-Schur phase of the V_a -> V_a^* isomorphism.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The sector whose indicator is asked. |
required |
Returns:
| Type | Description |
|---|---|
complex
|
|
FusionRules ¶
Bases: Protocol
The fusion ring: named sector labels, the unit, channels and multiplicities.
Deliberately not called a fusion category: fusion rules N^c_ab do not
determine one — a fusion category is the rules plus an associator
(AssociatorData) plus rigidity
(DualityData), and the same
rules with different associators are genuinely different categories
(Vec_G versus Vec_G^omega). dual is duality data and lives on
DualityData; the label-level dual map every
provider carries is annotated through _DualFusionRules where it is
actually read.
Notes
"Fusion category" is the name of a combination — FusionRules +
AssociatorData + DualityData with tenet.symmetry.coherence.validate_pentagon
and validate_snake passing — and it is a name in the docs, never a class
here.
fusion ¶
The fusion channels of a x b, in the provider's canonical order.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The two sectors fused, in this provider's own sector type. |
required |
b
|
Sector
|
The two sectors fused, in this provider's own sector type. |
required |
Returns:
| Type | Description |
|---|---|
tuple of Sector
|
Every |
n_symbol ¶
Multiplicity N^c_ab. Multiplicity-free providers return 0 or 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The two sectors fused. |
required |
b
|
Sector
|
The two sectors fused. |
required |
c
|
Sector
|
The candidate fusion channel. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The number of independent vertices |
PermutationCoefficients ¶
Bases: Protocol
Providers that can expand a fusion tree with permuted uncoupled lines.
permute_tree ¶
permute_tree(
tree: FusionTree, perm: tuple[int, ...]
) -> tuple[tuple[FusionTree, complex], ...]
((tree', coeff), ...) with the permuted tree equal to Σ coeff · tree'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tree
|
FusionTree
|
The left-associated tree whose uncoupled lines are permuted. |
required |
perm
|
tuple of int
|
|
required |
Returns:
| Type | Description |
|---|---|
tuple of (FusionTree, complex)
|
The expansion of the permuted tree over canonical left-associated trees with the same coupled sector. |
Notes
The coefficient stays scalar even under n_symbol > 1, because
the multiplicity label lives inside the tree: a matrix-valued F is
still one number per (tree, tree') pair, it just makes the
expansion longer.
PivotalData ¶
Bases: Protocol
Marker for the pivotal convention the bend helpers hardcode.
The pivotal isomorphism today is one fixed choice — TensorKit's
bendright, the sqrt(qdim(c)/qdim(a)) split of the bend's
normalization in bend_braided — shared by every provider, so this
protocol carries no method yet and every provider satisfies it.
Notes
It exists so the operations that depend on a pivotal structure
(bend, full_trace) can name it in their contracts now; a
non-spherical pivotal provider is the trigger for giving it a real method
(the per-object pivotal phase) instead of the hardcoded expression.
QuantumDimensionData ¶
Bases: Protocol
Providers that define a quantum dimension.
Notes
qdim need not be an integer (Fibonacci's tau has qdim == phi)
and is independent of any dense expansion
(ClebschGordanData).
qdim ¶
qdim(a: Sector) -> float
The quantum dimension d_a.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The sector whose quantum dimension is asked. |
required |
Returns:
| Type | Description |
|---|---|
float
|
|
RMatrixData ¶
Bases: Protocol
Array-valued braiding — BraidingData's
sibling for N^c_ab > 1.
r_matrix ¶
R^{ab}_c, shape (N^c_ab, N^c_ba).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
a
|
Sector
|
The two braided sectors, in order. |
required |
b
|
Sector
|
The two braided sectors, in order. |
required |
c
|
Sector
|
The fusion channel they are braided inside. |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
Shape |
Sector
dataclass
¶
Marker base for sector labels.
Subclasses are frozen, slotted, ordered dataclasses, so hashing and canonical
sorting come for free. Comparison is only defined within one sector type;
comparing different sector types raises TypeError.
StructureChangingError ¶
Bases: TypeError
Raised when an operation whose output structure depends on block values is asked to run inside a traced (jit/grad/vmap) region.
Invariants 9 and 10: the library
never hides the distinction between a shape-static operation and one that
decides its own output structure from the numbers. Lives here next to
CapabilityError, subclasses TypeError
for the same reason it does, and is exported from tenet.
TrivialProvider
dataclass
¶
Degenerate reference provider: one sector, everything trivial.
permute_tree ¶
permute_tree(
tree: FusionTree, perm: tuple[int, ...]
) -> tuple[tuple[FusionTree, complex], ...]
One term, coefficient 1: there is a single sector and F = R = 1.
bend_right ¶
bend_right(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
One term, coefficient 1: B follows from F ≡ 1 and dim ≡ 1.
TwistData ¶
Bases: Protocol
Providers that supply the ribbon twist theta.
Notes
Separate from the chi * theta flip product, because closed loops
(trace, adjoint, any fermion loop) need the bare twist. 1 for
every symmetric bosonic category, (-1)^parity
for fermion parity, e^{4 pi i / 5} on Fibonacci's tau.
FZ2Provider
dataclass
¶
Z2 fusion with fermionic braiding: SVect. Abelian, multiplicity-free, d = 1.
Use the module-level singleton fZ2 rather than
constructing one; name is an identity label that participates in
equality, not a configuration knob. The capability contract of every
method is documented once, on the protocols in tenet.symmetry —
only behaviour that differs from a protocol carries a docstring here,
and the whole fermionic content is the Koszul sign in
permute_tree and the -1
in twist.
Examples:
>>> from tenet.symmetry import fZ2, FZ2Sector
>>> fZ2.fusion(FZ2Sector(1), FZ2Sector(1))
(FZ2Sector(parity=0),)
>>> int(fZ2.twist(FZ2Sector(1))) # the sign a closed odd loop pays
-1
>>> int(fZ2.frobenius_schur(FZ2Sector(1))) # the FS phase stays +1
1
cgc ¶
Shape (1, 1, 1, 1), read-only. Raises on a fusion-forbidden triple.
permute_tree ¶
permute_tree(
tree: FusionTree, perm: tuple[int, ...]
) -> tuple[tuple[FusionTree, complex], ...]
Exactly one term: the tree is unique per (uncoupled, coupled) and the
coefficient is the Koszul sign of perm restricted to the odd lines.
The coefficient reads only the uncoupled parities — not the coupled sector
and not the inner lines — because F ≡ 1 makes the tree shape drop out.
That is not true for SU(2), whose exchange sign (-1)^(j_a+j_b-j_c)
reads the fused channel; the spine may be ignored here and nowhere else.
bend_right ¶
bend_right(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
One term, coefficient 1: dim = 1, B = N, FS = +1.
TensorKitSectors defines no frobenius_schur_phase for FermionParity;
it falls through to frobenius_schur_phase_from_Fsymbol(a) =
sign(Fsymbol(a, dual(a), a, a, leftunit(a), rightunit(a))), and since
dual(f) = f and every allowed F-symbol is +1, FS(odd) == +1.
Bsymbol likewise falls through to Bsymbol_from_Fsymbol, giving +1.
https://github.com/QuantumKitHub/TensorKitSectors.jl/blob/bd1bf8296876103870d6158c0918987256396880/src/sectors.jl#L535-L543
The -1 of fermion parity lives in twist, not in FS — see the module
docstring.
z_matrix ¶
z_matrix(a: FZ2Sector) -> ndarray
Z = [[1]], read-only: V_a is one-dimensional and FS == +1.
FZ2Sector
dataclass
¶
Bases: Sector
A fermion-parity sector: parity in {0, 1}. 0 is even (the unit), 1 is odd.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parity
|
int
|
|
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
ProductProvider
dataclass
¶
The Deligne product of two or more providers. Frozen, hashable, array-free.
Every core quantity (unit, dual, fusion, n_symbol,
qdim, irrep_dim, cgc, the phases) is componentwise, so the
capability contract stays the one documented on the protocols in
tenet.symmetry; the methods here document only what forwarding adds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factors
|
tuple of provider
|
Two or more providers, in order; the order is the component order of every ProductSector. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If fewer than two factors are given — a one-factor product is a confusing no-op wrapper, not a feature. |
Examples:
>>> from tenet.symmetry import U1, U1Sector, Z2, Z2Sector
>>> from tenet.symmetry import ProductProvider, ProductSector
>>> p = ProductProvider((U1, Z2))
>>> p.name
'U1 x Z2'
>>> a = ProductSector((U1Sector(1), Z2Sector(1)))
>>> p.fusion(a, a)
(ProductSector(components=(U1Sector(charge=2), Z2Sector(parity=0))),)
>>> p.dual(a)
ProductSector(components=(U1Sector(charge=-1), Z2Sector(parity=1)))
Notes
Optional capabilities are forwarded iff every factor has them, and the
forwarding methods are defined unconditionally so that the error can name
the offending factor. The consequence is a real wart, handled by raising
rather than by lying: runtime_checkable protocols check only for the
presence of a method, so isinstance(ProductProvider((SU2, U1)),
PermutationCoefficients) is True even though the call raises. The call
is authoritative, isinstance is optimistic. Synthesizing a subclass per
capability combination — a factory for one product — is rejected on sight.
Equality and hashing are on factors alone; name is a property, not a
field, so it can never be a second source of truth.
fusion ¶
fusion(a: Sector, b: Sector) -> tuple[ProductSector, ...]
Componentwise, and ascending: itertools.product is lexicographic and
ProductSector compares
lexicographically, so the canonical order
of each factor's fusion survives (asserted by test, not assumed).
frobenius_schur ¶
frobenius_schur(a: Sector) -> complex
chi of a Deligne product factorizes, like every other phase.
cgc ¶
Shape (prod d_a, prod d_b, prod d_c, prod n), the iterated outer
product of the factors' cgcs flattened in C order — which is exactly
the mixed radix of mu_encode. Raises ValueError (from the
factor) on a fusion-forbidden triple.
z_matrix ¶
z_matrix(a: Sector) -> ndarray
Z_a: V_a -> V_a^*, the Kronecker product of the factors' duality maps.
Flattened in the same C order as cgc, and
for the same reason: np.kron of the factor matrices is the mixed radix with
the leading factor most significant, so the two flattenings agree by construction
rather than by matching arithmetic.
bend_right ¶
bend_right(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
The distributed product of the factors' bends, as permute_tree is.
A bend moves one line from the end of the output tree to the end of the input
tree, and in a product that one line carries one component per factor, each
moving under its own factor's bending coefficient. So the bent product key is
⊗_i (Σ_α c_α^(i) k_α^(i)), which multiplies out to this. Written for many
terms per factor because SU(2)'s bend reaches more than one.
The dual flag is passed through unchanged rather than distributed: each
factor applies its own Frobenius-Schur phase, and the product's
frobenius_schur is exactly the
product of those, so the phases compose to the right one without being counted
here as well.
bend_left ¶
bend_left(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
The inverse direction of bend_right, assembled the same way.
permute_tree ¶
permute_tree(
tree: FusionTree, perm: tuple[int, ...]
) -> tuple[tuple[FusionTree, complex], ...]
The distributed product of the factors' expansions.
The braiding of a Deligne product is the tensor product of the factors'
braidings and the associator factorizes, so the permuted product tree is
literally ⊗_i (Σ_α c_α^(i) t_α^(i)), which multiplies out to this.
Written for many terms per factor, which SU(2)'s braid expansion actually
reaches — a single-term special case would be a correctness trap.
ProductSector
dataclass
¶
ProductSector(components: tuple[Sector, ...])
Bases: Sector
One sector per factor, in factor order. Ordering is lexicographic by component.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
components
|
tuple of Sector
|
One sector per factor of the ProductProvider, in factor order. |
required |
SU2Provider
dataclass
¶
SU(2) provider. Array-free and hashable; CG arrays live in a module-level cache.
Use the module-level singleton SU2 rather than
constructing one; name is an identity label that participates in
equality, not a configuration knob. The capability contract of every
method is documented once, on the protocols in tenet.symmetry —
only behaviour that differs from a protocol carries a docstring here.
Examples:
>>> from tenet.symmetry import SU2, SU2Sector
>>> half = SU2Sector(1) # two_j = 1 is spin-1/2
>>> SU2.fusion(half, half) # singlet and triplet
(SU2Sector(two_j=0), SU2Sector(two_j=2))
>>> SU2.irrep_dim(SU2Sector(2))
3
>>> int(SU2.frobenius_schur(half)) # half-integer spin: chi = -1
-1
fusion ¶
|ja-jb| <= jc <= ja+jb in integer steps, ascending in two_j.
cgc ¶
Shape (d_a, d_b, d_c, 1), read-only; magnetic indices descending.
The trailing axis is the multiplicity label mu; SU(2) is multiplicity-free
so it always has size 1, but consumers must still index it.
f_symbol ¶
f_symbol(
a: SU2Sector,
b: SU2Sector,
c: SU2Sector,
d: SU2Sector,
e: SU2Sector,
f: SU2Sector,
) -> float
[F^{abc}_d]_{e,f}; e is the inner line of ((ab)c), f of (a(bc)).
Real in this gauge, so downstream conjugation of domain-side coefficients
is a no-op. Raises if any vertex has n_symbol > 1 (unreachable for
SU(2), asserted so the scalar-valued contract is not merely documentation).
Exactly 0.0 when any of the four vertices is empty. That guard is load-bearing:
racah raises ValueError on an N = 0 vertex, where SU(2)'s contract is a
true zero.
r_symbol ¶
R^{ab}_c = (-1)^(ja + jb - jc), exactly +-1; SU(2) braiding is symmetric.
racah's exact tier returns the closed-form sign directly, so the float is already
exactly +-1.0; it is still snapped to int behind the unit-modulus assert,
because the exact integer is the contract every braid phase in
permute_braided_tree multiplies through and the assert is what would catch a
tier that stopped being exact.
permute_tree ¶
permute_tree(
tree: FusionTree, perm: tuple[int, ...]
) -> tuple[tuple[FusionTree, complex], ...]
Artin-braid expansion; SU(2) is symmetric, so perm fixes the braid.
bend_right ¶
bend_right(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
One term: sqrt(qdim(c)/qdim(a))·B(a,b,c), times chi if already dual.
frobenius_schur ¶
frobenius_schur(a: SU2Sector) -> int
chi_a = (-1)^(2j): +1 for integer spin, -1 for half-integer.
twist ¶
twist(a: SU2Sector) -> int
theta_a = 1: SU(2) braiding is symmetric, so the twist is trivial.
z_matrix ¶
z_matrix(a: SU2Sector) -> ndarray
Z_a: V_a -> V_a^*, shape (d_a, d_dual(a)) == (d_a, d_a); read-only.
Z[i, d_a - 1 - i] = (-1)**i in the descending-m basis of
cgc. Derived, not declared: the singlet in
V_j (x) V_j is the duality pairing, so it cannot drift out of gauge with
the CG tensors to_dense contracts it against — see
tenet.symmetry._sun_coeff.z_matrix. Not the identity for two_j >= 1,
even though dual(a) == a.
branch ¶
branch(
target: FusionRules, a: SU2Sector
) -> tuple[Sector, ...]
SU(2) -> U(1): the magnetic quantum numbers, doubled, descending.
cgc's magnetic indices run
descending, so index k of V_j
carries S_z = j - k and charge 2 S_z = two_j - 2 k. Identical to
froSTspin's np.arange(irr - 1, -irr - 1, -2).
Dual-agnostic: on a dual leg to_dense applies
z_matrix,
an antidiagonal ±1 signed permutation, so reversing the magnetic
order and negating the weight are the same operation and cancel.
SU2Sector
dataclass
¶
Bases: Sector
An SU(2) irrep labelled by two_j = 2j >= 0.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
two_j
|
int
|
The doubled spin, so labels stay exact integers: |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
SUNProvider
dataclass
¶
SU(N) provider for n >= 2. Array-free and hashable; arrays come from racah.
SUNProvider(3) is SU(3). Every sector-taking method validates that the
Dynkin label has n - 1 entries, so feeding an SU(4) sector to an SU(3)
provider fails at the first query rather than producing nonsense.
The capability contract of every method is documented once, on the
protocols in tenet.symmetry — only behaviour that differs from a
protocol carries a docstring here. Every coefficient (fusion, CG, F/R/B
matrices, FS) is delegated to the racah crate through
tenet.symmetry._sun_coeff, whose fingerprint is the gauge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
n
|
int
|
The |
required |
name
|
str
|
An identity label that participates in equality, not a configuration knob; leave it at the default. |
'SUN'
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
Examples:
>>> from tenet.symmetry.sun import SUNProvider, SUNSector
>>> su3 = SUNProvider(3)
>>> f, fbar = SUNSector((1, 0)), SUNSector((0, 1))
>>> su3.fusion(f, fbar) # 3 x 3bar = 1 + 8
(SUNSector(dynkin=(0, 0)), SUNSector(dynkin=(1, 1)))
>>> adj = SUNSector((1, 1))
>>> su3.n_symbol(adj, adj, adj) # 8 x 8 -> 8 has multiplicity 2
2
>>> su3.dual(f) # the conjugate irrep
SUNSector(dynkin=(0, 1))
dual ¶
The conjugate irrep: the Dynkin label reversed. Not the identity for N > 2.
fusion ¶
The Littlewood-Richardson decomposition of a x b, ascending in Dynkin order.
cgc ¶
Shape (d_a, d_b, d_c, N^c_ab), read-only; last axis is the multiplicity label.
f_matrix ¶
f_matrix(
a: SUNSector,
b: SUNSector,
c: SUNSector,
d: SUNSector,
e: SUNSector,
f: SUNSector,
) -> ndarray
[F^{abc}_d]_{e,f}, shape (N^e_ab, N^d_ec, N^f_bc, N^d_af), read-only.
r_matrix ¶
R^{ab}_c, shape (N^c_ab, N^c_ba), read-only.
b_matrix ¶
B^{ab}_c, shape (N^c_ab, N^a_{c,dual(b)}).
f_symbol ¶
f_symbol(
a: SUNSector,
b: SUNSector,
c: SUNSector,
d: SUNSector,
e: SUNSector,
f: SUNSector,
) -> float
[F^{abc}_d]_{e,f}. Raises if any vertex has n_symbol > 1.
r_symbol ¶
R^{ab}_c. Raises if N^c_ab > 1.
b_symbol ¶
B^{ab}_c. Raises if any vertex has n_symbol > 1.
frobenius_schur ¶
frobenius_schur(a: SUNSector) -> int
chi_a = sign([F^{a dual(a) a}_a]_{1,1}), the TensorKit definition.
twist ¶
twist(a: SUNSector) -> int
theta_a = 1: SU(N) braiding is symmetric, so the twist is trivial.
z_matrix ¶
z_matrix(a: SUNSector) -> ndarray
Z_a: V_a -> V_a^*, shape (d_a, d_dual(a)); read-only.
Derived from the CG singlet of V_a (x) V_dual(a), so it is in the same
gauge as cgc by construction. Never the identity for d_a > 1,
and its two axes index different sectors whenever dual(a) != a.
permute_tree ¶
permute_tree(
tree: FusionTree, perm: tuple[int, ...]
) -> tuple[tuple[FusionTree, complex], ...]
Artin-braid expansion; SU(N) is symmetric, so perm fixes the braid.
bend_right ¶
bend_right(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
sqrt(qdim(c)/qdim(a))·B(a,b,c), expanded over the new vertex's multiplicity.
SUNSector
dataclass
¶
Bases: Sector
An SU(N) irrep labelled by its Dynkin label (a_1, ..., a_{N-1}).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dynkin
|
tuple of int
|
The Dynkin label, one non-negative entry per node of the |
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
Examples:
>>> from tenet.symmetry.sun import SUNSector
>>> SUNSector((1, 0)) # the fundamental 3 of SU(3)
SUNSector(dynkin=(1, 0))
U1Provider
dataclass
¶
U(1): abelian, multiplicity-free, one-dimensional irreps.
Use the module-level singleton U1 rather than
constructing one; name is an identity label that participates in
equality, not a configuration knob. The capability contract of every
method is documented once, on the protocols in tenet.symmetry —
only behaviour that differs from a protocol carries a docstring here.
Examples:
>>> from tenet.symmetry import U1, U1Sector
>>> U1.fusion(U1Sector(-1), U1Sector(1))
(U1Sector(charge=0),)
>>> U1.n_symbol(U1Sector(1), U1Sector(1), U1Sector(2))
1
>>> U1.irrep_dim(U1Sector(5))
1
cgc ¶
Shape (1, 1, 1, 1), read-only. Raises on a fusion-forbidden triple.
permute_tree ¶
permute_tree(
tree: FusionTree, perm: tuple[int, ...]
) -> tuple[tuple[FusionTree, complex], ...]
One term, coefficient 1: charge addition is commutative and F = R = 1.
bend_right ¶
bend_right(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
One term, coefficient 1: for an Abelian irrep B = N, dim = 1, FS = 1.
z_matrix ¶
z_matrix(a: U1Sector) -> ndarray
Z = [[1]], read-only: V_q is one-dimensional and the FS phase is 1.
U1Sector
dataclass
¶
Bases: Sector
A U(1) irrep, labelled by its integer charge.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
charge
|
int
|
The conserved charge; negation is duality. |
required |
Examples:
>>> from tenet.symmetry import U1, U1Sector
>>> U1.fusion(U1Sector(1), U1Sector(2))
(U1Sector(charge=3),)
>>> U1.dual(U1Sector(1))
U1Sector(charge=-1)
Z2Provider
dataclass
¶
Z2 with bosonic braiding: Vect_Z2. Abelian, multiplicity-free, d = 1, R = +1.
Use the module-level singleton Z2 rather than
constructing one; name is an identity label that participates in
equality, not a configuration knob. The capability contract of every
method is documented once, on the protocols in tenet.symmetry —
only behaviour that differs from a protocol carries a docstring here.
Examples:
>>> from tenet.symmetry import Z2, Z2Sector
>>> Z2.fusion(Z2Sector(1), Z2Sector(1))
(Z2Sector(parity=0),)
>>> int(Z2.twist(Z2Sector(1))) # bosonic: no sign, unlike fermion parity
1
cgc ¶
Shape (1, 1, 1, 1), read-only. Raises on a fusion-forbidden triple.
permute_tree ¶
permute_tree(
tree: FusionTree, perm: tuple[int, ...]
) -> tuple[tuple[FusionTree, complex], ...]
One term, coefficient 1 — the braiding is symmetric and F = R = +1.
This is the one method that separates this provider from the fermionic one
in fz2.py, which returns the same tree with a Koszul sign. See the
module docstring: the permission to delegate is the braiding, never the
uniqueness of the fusion.
bend_right ¶
bend_right(
key: FusionBlockKey, *, dual: bool
) -> tuple[tuple[FusionBlockKey, complex], ...]
One term, coefficient 1: for an Abelian irrep B = N, dim = 1, FS = 1.
z_matrix ¶
z_matrix(a: Z2Sector) -> ndarray
Z = [[1]], read-only: V_q is one-dimensional and the FS phase is 1.
Z2Sector
dataclass
¶
Bases: Sector
A Z2 charge: parity in {0, 1}. 0 is even (the unit), 1 is odd.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
parity
|
int
|
|
required |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If |
bend_braided ¶
bend_braided(
provider: _TreeBender,
key: FusionBlockKey,
*,
right: bool,
dual: bool,
) -> tuple[tuple[FusionBlockKey, complex], ...]
bend_right/bend_left for any provider supplying B, FS and qdim.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
FusionRules
|
A provider with DualityData,
FSIndicatorData,
QuantumDimensionData and
PivotalData (the |
required |
key
|
FusionBlockKey
|
The block key one line is moved across. |
required |
right
|
bool
|
|
required |
dual
|
bool
|
The moved leg's current dual flag; it keys the Frobenius-Schur factor. |
required |
Returns:
| Type | Description |
|---|---|
tuple of (FusionBlockKey, complex)
|
One term for a multiplicity-free provider; genuinely multi-term when the provider supplies BMatrixData. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the source tree is empty (rank 0), so there is no line to bend. |
CapabilityError
|
If the bent vertex has |
Notes
The source tree's last vertex is a x b -> c (a the unit when the
source has rank 1). The source loses b and re-couples to a; the
destination gains dual(b) at its end and couples to a too. Both
spines are recomputed from the new uncoupled tuples — the source spine is
truncated and the destination's old coupled sector becomes its new last inner
line — never relabelled.
The coefficient is TensorKit's bendright::
coeff = sqrt(qdim(c) / qdim(a)) · B(a, b, c) (× conj(chi_dual(b))
if the moved leg
was already dual)
and bend_left is bendleft: the same expression read off the input
tree, then conjugated — which is what makes it the exact inverse of
bend_right rather than a reciprocal guess (a unitary's inverse is its
conjugate transpose, and multiplicity-free bending is one entry per key).
Note the Frobenius-Schur factor is keyed on the moved leg's current flag,
so it appears once across a round trip, never twice and never zero times.
Single-term because the provider is multiplicity-free: one key in, one key
out. n_symbol > 1 needs matrix-valued coefficients, which a provider
supplies by also implementing BMatrixData; then the
destination's new vertex label is expanded over B's second axis instead
of being pinned to 0, and the result is genuinely multi-term. Without
that capability the existing CapabilityError still fires.
bend_unique ¶
bend_unique(
provider: _DualFusionRules,
key: FusionBlockKey,
*,
right: bool,
dual: bool,
) -> tuple[tuple[FusionBlockKey, complex], ...]
bend_right/bend_left for providers whose fusion is unique and B is 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
FusionRules
|
The provider whose keys are bent; it must have opted in by defining
|
required |
key
|
FusionBlockKey
|
The block key one line is moved across. |
required |
right
|
bool
|
|
required |
dual
|
bool
|
The moved leg's current dual flag. Accepted and provably ignored here — see Notes. |
required |
Returns:
| Type | Description |
|---|---|
tuple of (FusionBlockKey, complex)
|
Exactly one term: the recomputed key with coefficient |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the source tree is empty (rank 0), so there is no line to bend. |
CapabilityError
|
If dropping the moved line leaves uncoupled labels with anything other than exactly one coupled sector, or more than one tree — this helper is only correct for unique fusion. |
Notes
Shared by Trivial and U(1), exactly as
permute_unique_tree is. The
source tree loses its last uncoupled line, the destination tree gains
dual of it at the end, and the new coupled sector is the source tree's
last inner line (the unit when the source had rank 1). Both spines are
recomputed from the new uncoupled tuples, never relabelled.
The coefficient is sqrt(dim(c)/dim(a)) · B(a,b,c), times the conjugate
Frobenius-Schur phase of dual(b) when the moved line is already dual.
For every Abelian irrep all three factors are exactly 1 (all dim == 1,
B == N ∈ {0,1}, frobenius_schur_phase == 1), and Trivial reaches the
same value through F ≡ 1. So dual is accepted and provably ignored
here; a provider whose FS phase is not 1 must not route through this helper.
Not a capability check: a provider opts in by defining bend_right /
bend_left. Uniqueness of fusion alone is never permission (a fermionic
parity provider has unique fusion and a non-trivial bend).
permute_braided_tree
cached
¶
permute_braided_tree(
provider: _TreeBraider,
tree: FusionTree,
perm: tuple[int, ...],
) -> tuple[tuple[FusionTree, complex], ...]
permute_tree for any provider supplying F- and R-symbols.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
FusionRules
|
A provider with AssociatorData and BraidingData (checked with requires on entry). |
required |
tree
|
FusionTree
|
The left-associated tree whose uncoupled lines are permuted. |
required |
perm
|
tuple of int
|
|
required |
Returns:
| Type | Description |
|---|---|
tuple of (FusionTree, complex)
|
The accumulated |
Raises:
| Type | Description |
|---|---|
CapabilityError
|
If the provider lacks FusionRules, AssociatorData or BraidingData. |
Notes
Bubble-decomposes perm into adjacent transpositions (Artin generators) and
applies _artin_braid to each, accumulating a {tree: coeff} expansion.
Symmetric-category only: the caller gets no over/under choice, because
R == R**-1 for the providers this serves (invariant 12). A provider whose
braiding is genuinely chiral must not reuse this helper — it needs levels
and an explicit braid API.
The coefficients are conjugated nowhere, which is correct exactly while the
provider's gauge is real: TensorKit braids the domain-side (fusion) tree with
the conjugate coefficient, and for a complex-gauge provider the caller must
conjugate on the domain side. SU(2) in the racah/TensorKitSectors gauge has
real F and R, so the two agree and permutation_plan may treat the two
trees of a block key symmetrically.
No tolerance-based pruning: a structurally forbidden term is already exactly
0.0 through f_symbol, and the surviving float residues from the sum
over d cost plan size, never correctness.
permute_unique_tree ¶
permute_unique_tree(
provider: FusionRules,
tree: FusionTree,
perm: tuple[int, ...],
) -> tuple[tuple[FusionTree, complex], ...]
permute_tree for providers whose fusion is unique and whose F/R are 1.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
FusionRules
|
The provider whose trees are permuted; it must have opted in by
defining |
required |
tree
|
FusionTree
|
The left-associated tree whose uncoupled lines are permuted. |
required |
perm
|
tuple of int
|
|
required |
Returns:
| Type | Description |
|---|---|
tuple of (FusionTree, complex)
|
Exactly one term: the recomputed tree with coefficient |
Raises:
| Type | Description |
|---|---|
CapabilityError
|
If the permuted uncoupled labels admit anything other than exactly one tree coupling to the same sector — this helper is only correct for unique fusion. |
Notes
Shared by Trivial and U(1): permuting the uncoupled labels leaves exactly one left-associated tree with the same coupled sector, so the whole expansion is that tree with coefficient 1. The spine is recomputed rather than permuted, which is what makes this correct rather than a relabelling.
Not a capability check: a provider must opt in by defining permute_tree
(fermionic parity also has unique multiplicity-free fusion and still carries a
sign, so uniqueness alone must never be taken as permission).
requires ¶
Raise CapabilityError unless
provider implements capability.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
object
|
The provider gated, usually read off a leg's space. |
required |
capability
|
type
|
One of the |
required |
Raises:
| Type | Description |
|---|---|
CapabilityError
|
If |
Examples:
>>> from tenet.symmetry import U1, Z2, ClebschGordanData, BranchingRules, requires
>>> requires(U1, ClebschGordanData) # U(1) has CG tensors: no raise
>>> requires(Z2, BranchingRules)
Traceback (most recent call last):
...
tenet.symmetry.base.CapabilityError: Z2Provider does not provide capability BranchingRules
supports ¶
True iff provider implements capability — requires'
non-raising sibling, so the capability graph is queryable without
try/except CapabilityError.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
provider
|
object
|
The provider queried. |
required |
capability
|
type
|
One of the |
required |
Returns:
| Type | Description |
|---|---|
bool
|
Whether |
Examples: