Skip to content

Tensor products and Clebsch–Gordan

tensor

tensor.py

Tensor products and Clebsch–Gordan decomposition of U_q(sl_2) representations.

The tensor product action is induced by the coproduct Δ:

X . (v ⊗ w) = Δ(X) . (v ⊗ w)

Thus the generators act on V_m ⊗ V_n as follows:

E . (v ⊗ w) = E v ⊗ w + K v ⊗ E w
F . (v ⊗ w) = F v ⊗ K^{-1} w + v ⊗ F w
K . (v ⊗ w) = K v ⊗ K w

Clebsch–Gordan decomposition (the same structure as in the classical case):

V_m ⊗ V_n  ≅  V_{m+n}  ⊕  V_{m+n-2}  ⊕  ...  ⊕  V_{|m-n|}

This module constructs the action matrices on V_m ⊗ V_n and explicitly computes a highest-weight vector for each direct-sum component.

TensorRepresentation dataclass

Container for V_m ⊗ V_n.

Source code in quantum_group/tensor.py
37
38
39
40
41
42
43
44
45
46
@dataclass
class TensorRepresentation:
    """Container for V_m ⊗ V_n."""
    m: int
    n: int
    dim: int
    E: sp.Matrix
    F: sp.Matrix
    K: sp.Matrix
    K_inv: sp.Matrix

tensor_product

tensor_product(repA: Representation, repB: Representation) -> TensorRepresentation

Construct the matrix action of generators on V_m ⊗ V_n via Δ.

Basis order: e_{i,j} = v_i^A ⊗ v_j^B, lexicographic (first i, then j).

Source code in quantum_group/tensor.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def tensor_product(repA: Representation, repB: Representation) -> TensorRepresentation:
    """Construct the matrix action of generators on V_m ⊗ V_n via Δ.

    Basis order: e_{i,j} = v_i^A ⊗ v_j^B, lexicographic (first i, then j).
    """
    nA, nB = repA.dim, repB.dim
    IA = sp.eye(nA)
    IB = sp.eye(nB)

    E = kron(repA.E, IB) + kron(repA.K, repB.E)
    F = kron(repA.F, repB.K_inv) + kron(IA, repB.F)
    K = kron(repA.K, repB.K)
    K_inv = kron(repA.K_inv, repB.K_inv)

    return TensorRepresentation(
        m=repA.n, n=repB.n, dim=nA * nB,
        E=sp.simplify(E), F=sp.simplify(F),
        K=sp.simplify(K), K_inv=sp.simplify(K_inv),
    )

cg_summands

cg_summands(m: int, n: int) -> list[int]

List the k values appearing in V_m ⊗ V_n = ⊕ V_k.

k = |m-n|, |m-n|+2, ..., m+n (all have the parity of m+n).

Source code in quantum_group/tensor.py
74
75
76
77
78
79
80
81
82
83
def cg_summands(m: int, n: int) -> list[int]:
    """List the k values appearing in V_m ⊗ V_n = ⊕ V_k.

    k = |m-n|, |m-n|+2, ..., m+n  (all have the parity of m+n).
    """
    m = as_int(m, "m", minimum=0)
    n = as_int(n, "n", minimum=0)
    lo = abs(m - n)
    hi = m + n
    return list(range(lo, hi + 1, 2))

find_highest_weight_vectors

find_highest_weight_vectors(tensor_rep: TensorRepresentation, q_sym: Expr = default_q) -> list[tuple[int, sp.Matrix]]

Find highest-weight vectors for generic q in V_m ⊗ V_n.

Both factors must use the same parameter. Candidate weights come from the generic Clebsch–Gordan rule; this is not a root-of-unity classifier. q_sym is retained for API compatibility; exponent labels determine the subspaces and the supplied matrices determine their kernels.

A vector v has highest weight k when: E . v = 0 and K . v = q^k v.

Returns a list of (k, v) pairs in descending order of k.

Algorithm: For each candidate k = |m-n|, |m-n|+2, ..., m+n, select the weight subspace with K-eigenvalue q^k by its diagonal indices, then compute the kernel of E on that subspace.

Source code in quantum_group/tensor.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def find_highest_weight_vectors(
    tensor_rep: TensorRepresentation,
    q_sym: sp.Expr = default_q,
) -> list[tuple[int, sp.Matrix]]:
    """Find highest-weight vectors for generic q in V_m ⊗ V_n.

    Both factors must use the same parameter. Candidate weights come from
    the generic Clebsch–Gordan rule; this is not a root-of-unity classifier.
    ``q_sym`` is retained for API compatibility; exponent labels determine
    the subspaces and the supplied matrices determine their kernels.

    A vector v has highest weight k when:
        E . v = 0     and     K . v = q^k v.

    Returns a list of (k, v) pairs in descending order of k.

    Algorithm:
        For each candidate k = |m-n|, |m-n|+2, ..., m+n, select the
        weight subspace with K-eigenvalue q^k by its diagonal indices,
        then compute the kernel of E on that subspace.
    """
    m, n = tensor_rep.m, tensor_rep.n
    summands = cg_summands(m, n)

    # K is diagonal; compute its q-exponent for each basis vector.
    K_diag_exponents = []
    for i in range(m + 1):
        for j in range(n + 1):
            # K-exponents: m - 2i for v_i^A and n - 2j for v_j^B.
            K_diag_exponents.append((m - 2 * i) + (n - 2 * j))

    results: list[tuple[int, sp.Matrix]] = []
    for k in reversed(summands):
        # Collect indices of the weight-k subspace.
        idx = [t for t, w in enumerate(K_diag_exponents) if w == k]
        if not idx:
            continue
        # Restrict E to this indexed subspace and find its kernel.
        # E maps this weight subspace to the next one. For its full kernel,
        # inspect the columns indexed by idx across all target rows.
        E_cols = sp.Matrix([[tensor_rep.E[r, c] for c in idx]
                            for r in range(tensor_rep.dim)])
        null = E_cols.nullspace()
        # Embed each nullspace vector back into the full V_m ⊗ V_n basis.
        for vec in null:
            full = sp.zeros(tensor_rep.dim, 1)
            for local_i, global_i in enumerate(idx):
                full[global_i, 0] = vec[local_i, 0]
            full = sp.simplify(full)
            results.append((k, full))

    return results

cg_decomposition_summary

cg_decomposition_summary(m: int, n: int) -> str

Return a readable summary of the V_m ⊗ V_n decomposition.

Source code in quantum_group/tensor.py
140
141
142
143
144
145
def cg_decomposition_summary(m: int, n: int) -> str:
    """Return a readable summary of the V_m ⊗ V_n decomposition."""
    summands = cg_summands(m, n)
    parts = " ⊕ ".join(f"V_{k}" for k in reversed(summands))
    total = sum(k + 1 for k in summands)
    return f"V_{m} ⊗ V_{n} ≅ {parts}    (dimension: {(m+1)*(n+1)} = {total})"