A Python library for constructing linear constraints on tensors under transformations using Kronecker products. Supports operation-dependent T-parity, P-parity, and index-permutation symmetries, with automatic nullspace computation via SVD and LaTeX output.
This library constructs linear constraints for order-r tensors in dimension n under spin-group operations (Rs, Rr) of the form:
Tᵢ₁…ᵢᵣ = sign_T · sign_P · Rrᵢ₁ⱼ₁ … Rrᵢᵣⱼᵣ Tⱼ₁…ⱼᵣ
where the per-operation sign factors are computed from matrix determinants:
sign_T = det(Rs) ** T_constraint
sign_P = det(Rr) ** P_constraint
Rsis the spin-space matrix:det(Rs) = -1contributes an effective time-reversal sign for T-odd tensors (T_constraint = 1).Rris the real-space matrix applied to every tensor axis:det(Rr) = -1contributes a parity sign for P-odd tensors (P_constraint = 1).- The real-space action
Rr ⊗ Rr ⊗ ... ⊗ Rralways drives the tensor transformation;Rsonly contributes a sign.
The library builds constraint matrices, computes their nullspace using SVD (not row reduction), and outputs readable LaTeX relations with automatic index-to-symbol mapping.
pip install spintensorOr install in development mode:
pip install -e .- Operation-Dependent Sign Logic: Per-operation sign factors
sign_T = det(Rs)**T_constraintandsign_P = det(Rr)**P_constraint(not global fixed values) - Kronecker Product Transformations: Efficiently build transformation matrices using Kronecker products of the real-space matrix
Rr - T-parity and P-parity: Support for time reversal and spatial parity symmetries
- Index Permutation Symmetries: Handle symmetric, antisymmetric, and general permutation symmetries
- SVD-based Nullspace: Numerically stable nullspace computation using Singular Value Decomposition
- LaTeX Output: Automatic generation of readable constraint relations with smart index notation
import numpy as np
from spintensor import solve_tensor_constraints
# Define operations in [Rs, Rr] format
# Rs: spin-space matrix; Rr: real-space matrix
n = 3 # dimension
r = 2 # tensor rank
# Spin reflection (det(Rs) = -1) in spin space, identity in real space
Rs = np.diag([1., 1., -1.]) # det = -1
Rr = np.eye(3) # det = +1
# T-odd tensor: sign_T = det(Rs)**1 = -1
A, nullspace, relations, components = solve_tensor_constraints(
[[Rs, Rr]], n, r, T_constraint=1, P_constraint=0
)
print(f"Independent components: {nullspace.shape[1]}")
for rel in relations:
print(f" {rel}")All transformation formats are supported. The [Rs, Rr] format is the primary API for the new per-operation sign convention:
# Each entry is [spin_matrix, real_space_matrix]
transformations = [
[Rs1, Rr1], # sign_T = det(Rs1)**T_constraint, sign_P = det(Rr1)**P_constraint
[Rs2, Rr2],
]With spin_axes set, Rs is also applied to the specified tensor axes.
# Tflag=True means the operation is anti-unitary (contributes sign_T=-1 when T_constraint=1)
transformations = [
[False, Rr_unitary], # sign_T = +1
[True, Rr_antiunitary], # sign_T = -1
]# Single numpy array: treated as Rs = identity (det=+1, no T sign)
transformations = [Rr1, Rr2] # sign_T = +1 always# Explicit parity values (backward compatible)
transformations = [(R, s_T, s_P)]solve_tensor_constraints(transformations, n, r, symmetries=None, antisymmetries=None, tol=1e-10, spin_axes=None, T_constraint=0, det_tol=1e-6, symbol='\\sigma', extra_constraints=None, P_constraint=0)
Complete solver for tensor constraints.
Parameters:
transformations: List of transformations in any supported formatn: int, dimension of the tensor spacer: int, order (rank) of the tensorsymmetries: list of tuples, optional index permutations for symmetric constraints (T_perm = T)antisymmetries: list of tuples, optional index permutations for antisymmetric constraints (T_perm = -T)tol: float, tolerance for numerical operationsspin_axes: optional tuple of axis indices for spin axes (used with [Rs, Rr] format)T_constraint: int, exponent for time-reversal sign.sign_T = det(Rs)**T_constraint. Use1for T-odd tensors (default0)P_constraint: int, exponent for parity sign.sign_P = det(Rr)**P_constraint. Use1for P-odd/axial tensors (default0)det_tol: float, tolerance for determinant comparison to ±1
Returns:
constraint_matrix: numpy array, full constraint matrixnullspace_basis: numpy array, columns are basis vectorslatex_relations: list of strings, formatted LaTeX relationstensor_components: nested list of component expressions
Solve for QMD (quadrupole moment derivative) tensors: T-odd, rank-3, with last-two-index symmetry.
Solve for IMD tensors: T-odd, rank-3, with both last-two and first-two index symmetry.
Solve for BCD (Berry curvature dipole) tensors: T-even, rank-3, with last-two-index symmetry and optional cyclic sum constraint.
Build the full constraint matrix from transformations and symmetries.
Compute nullspace of matrix A using SVD.
Format nullspace basis vectors as readable LaTeX relations.
import numpy as np
from spintensor import solve_tensor_constraints
# Operations in [Rs, Rr] format
# T-odd tensor: sign_T = det(Rs)**1
ops = [
[np.diag([1., 1., 1.]), np.diag([1., 1., 1.])], # identity
[np.diag([1., 1., -1.]), np.diag([1., 1.,-1.])], # z-mirror: det(Rs)=-1 → s_T=-1
]
A, nullspace, relations, comps = solve_tensor_constraints(
ops, n=3, r=2, T_constraint=1, P_constraint=0
)
print(f"Independent components: {nullspace.shape[1]}")import numpy as np
from spintensor import solve_qmd
# Operations in [Rs, Rr] format
# Mirror op: det(Rs) = -1 → T-odd sign applies
s = np.sqrt(3) / 2
ops = [
[np.eye(3), np.eye(3)],
[np.array([[-0.5, s, 0], [s, 0.5, 0], [0, 0, 1]]),
np.array([[-0.5, s, 0], [s, 0.5, 0], [0, 0, 1]])],
# ... more operations
]
A, nullspace, relations, comps = solve_qmd(ops)import numpy as np
from spintensor import solve_tensor_constraints
# P_constraint=1: sign_P = det(Rr)**1
# For a reflection with det(Rr)=-1, this adds a -1 sign factor
R_reflect = np.diag([1., 1., -1.])
A, nullspace, relations, comps = solve_tensor_constraints(
[[np.eye(3), R_reflect]], n=3, r=2,
T_constraint=0, P_constraint=1
)import numpy as np
from spintensor import solve_tensor_constraints
R_z = np.array([[-1, 0, 0], [0, -1, 0], [0, 0, 1]])
# Legacy tuple format: (R, s_T, s_P) — explicit parity values
transformations = [(R_z, 1.0, 1.0)]
A, nullspace, relations, comps = solve_tensor_constraints(
transformations, n=3, r=2, symmetries=[(1, 0)]
)For each operation (Rs, Rr), the transformation matrix acting on the flattened tensor is:
M = sign_T · sign_P · (Rr ⊗ Rr ⊗ ... ⊗ Rr)
where:
sign_T = det(Rs) ** T_constraint
sign_P = det(Rr) ** P_constraint
The constraint for a symmetry-invariant tensor is (M - I) · T = 0.
Physical interpretation:
det(Rs) = -1: The spin-space operation flips the spin (effective time-reversal character). For T-odd tensors (T_constraint=1), this contributes a factor of-1.det(Rr) = -1: The real-space operation is an improper rotation (parity-odd). For P-odd/axial tensors (P_constraint=1), this contributes a factor of-1.- These factors are per-operation — each operation in the symmetry group can independently flip or not flip each parity sign.
Index permutation symmetries impose additional constraints:
(P_perm - I) · T = 0 (symmetric)
(P_perm + I) · T = 0 (antisymmetric)
The solution space consists of all tensors satisfying the constraints. This is the nullspace of the combined constraint matrix A, computed via SVD:
A = U · Σ · Vᵀ
The nullspace is spanned by columns of V corresponding to zero singular values. The basis is further reduced to RREF form for clean, canonical coefficients.
Relations are output in LaTeX format with automatic index notation:
- For n ≤ 4: uses letters (x, y, z, w)
- For n > 4: uses numbers (1, 2, 3, ...)
Example output:
\sigma_{xxy} = \sigma_{xyx} = \sigma_{yxx} = -\sigma_{yyy}
\sigma_{xyz} = \sigma_{xzy} = -\sigma_{yxz} = -\sigma_{yzx}
- Python ≥ 3.7
- NumPy ≥ 1.19.0
MIT License
Contributions are welcome! Please feel free to submit a Pull Request.
If you use this library in your research, please cite:
@software{spintensor,
title = {SpinTensor: Linear Constraints for Tensors Under Transformations},
author = {Connor1y},
year = {2026},
url = {https://github.com/Connor1y/spintensor}
}