torch_vn_algebra: block-diagonal operators, symmetry sectors and quantum channels in PyTorch¶
torch_vn_algebra is a Python library for batched computation with block-diagonal matrices in symmetry sectors, the finite-dimensional Type I von Neumann algebras ⊕ M_k(ℂ), in PyTorch on CPU and GPU. It provides random operators and Haar-random unitaries, density matrices, quantum channels (Kraus, Choi, Stinespring), Schrödinger and Lindblad dynamics, and spin chains in U(1), momentum and SU(2) sectors; it computes the Wedderburn (block-diagonal) decomposition and the commutant of the algebra generated by given matrices or by a group representation, conditional expectations and Jones indices, Jordan algebras and quantum Rényi divergences. Operators, states and channels are batched over thousands of samples, differentiable with autograd, and available in single or double precision.
It is written for physicists (superselection and charge sectors, exact diagonalisation, open quantum systems), quantum-information researchers (states, channels, entropies, error correction), mathematicians (operator algebras, random matrices, representation theory) and machine-learning researchers (equivariant linear layers as commutants). The theory behind every function is in self-contained lecture notes with complete proofs, and the library is shown at work in a tutorial whose code is run by the tests, both in English, Spanish, French, German, Chinese, Japanese and Russian; documentation website: https://andre-a-no.github.io/von_neumann_type_i/.
The algebras are direct sums of full matrix algebras:
math
\mathcal M = \bigoplus_{c=1}^{C} M_{k_c}(\mathbb C)\quad\text{acting on}\quad \mathcal H=\bigoplus_{c=1}^{C}\mathbb C^{k_c}.
(Nominal factor sizes n_factors \(n_c\ge k_c\) describe computations in the corner \(p\mathcal M_0p\) of a larger
algebra \(\mathcal M_0=\bigoplus_c M_{n_c}\) with rank-\(k_c\) projections \(p_c\); everything refers to the corner.)
Operators are stored as one tensor of shape (batch, C, k_max, k_max): the batch axis holds
samples (or optimisation starts), the sector axis holds the direct summands, and block c occupies the
top-left k_c × k_c corner (the rest is zero padding).
Companion paper: I. Nikolaeva, A. Novikov, Finite-Dimensional Type I von Neumann Algebras in
PyTorch: A GPU-Accelerated Framework for Random Block-Diagonal Operators, arXiv:2606.15882
(source and PDF in man/article/).
This README is the entry point of the repository:
| you want to | go to |
|---|---|
| use the library | Installation, Quick start, examples/ |
| learn the theory | the lecture notes: finite-dimensional von Neumann algebras, self-contained, with complete proofs (7 languages) |
| learn the library | the tutorial: the library in the context of the theory, chapter by chapter, with cross-links to the theory (7 languages) |
| understand the physics behind the models | the boxes Physical meaning in the lecture notes, e.g. symmetries and charge sectors, dynamics, spin chains |
| compare with other software | When to use it, and alternatives |
| read the paper | man/article/main.pdf (source man/article/main.tex) |
| reproduce every number of the paper | Reproducing the paper |
| cite it | Citation |
Features¶
- Block-diagonal operators in batches – an operator of ⊕c M is one tensor
(batch, C, k_max, k_max); products, adjoints, traces, norms and functional calculus run as batched kernels on CPU or CUDA GPU (Tutorial 1, 2.5). - Random matrices and Haar-random unitaries – Haar measure on U(n), O(n), SU(n), SO(n), COE, CSE, random operators with a prescribed spectrum, GUE/GOE level statistics, Weingarten-calculus checks (Tutorial 5).
- Quantum states and entropy – density matrices (Hilbert–Schmidt, Bures, fixed rank), Gibbs states, von Neumann entropy, relative entropy, fidelity, trace distance (Tutorial 3).
- Quantum channels – Kraus operators, Choi matrix, Stinespring dilation, superoperator; dephasing, depolarizing, amplitude damping, random channels, channels between sectors (Tutorial 6).
- Tensor products and entanglement – partial trace, fusion of U(1) charges, entanglement entropy, Page's formula (Tutorial 7).
- Schrödinger and Lindblad (GKSL) master equations – exact propagators, RK4, time steps that are CPTP channels, quantum jumps between sectors, batched Hamiltonians (Tutorial 8).
- Spin chains and exact diagonalisation in symmetry sectors – XXZ and Heisenberg chains in U(1) magnetisation, momentum and SU(2) sectors, Lanczos ground states, Krylov time evolution, disorder averages, level-spacing ratio (Tutorial 9).
- Wedderburn decomposition, commutants and group representations – block diagonalisation of the matrix *-algebra generated by matrices, a Lie algebra or a finite group (isotypic decomposition, multiplicities, Schur–Weyl duality), real/complex/quaternionic type, Clebsch–Gordan coefficients and the Wigner–Eckart theorem (Tutorial 4, Appendix B).
- Quantum error correction – noiseless subsystems, decoherence-free subspaces, the Knill–Laflamme conditions, operator-algebra error correction (Tutorial 10).
- Conditional expectations and inclusions of algebras – Jones index, basic construction, Pimsner–Popa constants,
Bratteli diagrams (Tutorial 2.6,
example_inclusions.py). - Quantum Rényi divergences – Petz and sandwiched Rényi divergences, Umegaki relative entropy, D_max, Petz recovery
maps (
example_divergences.py). - Constrained optimisation – batched multistart optimisation over unitaries and positive operators, trace inequalities (Tutorial 11).
- Equivariant machine learning – equivariant linear layers as the commutant of a group representation (Tutorial 12).
- Jordan algebras – Jordan product, quadratic representation, spin factors and the Lorentz cone, the Jordan algebra generated by matrices (Tutorial 13).
When to use it, and alternatives¶
torch_vn_algebra pays off when the block structure is part of the problem: many sectors of different sizes (superselection or charge sectors, isotypic components of a symmetry), many samples or optimisation starts at once, and quantities that are defined by the algebra (its traces, states, centre, conditional expectations, channels between sectors). For one dense matrix at a time, for very large sparse systems, or for tensor networks, the established tools below are the better choice; the sections What to compare with of the tutorial compare them in detail.
| task | established tools | what torch_vn_algebra adds |
|---|---|---|
| open quantum systems (8.6) | QuTiP (mesolve, mcsolve), dynamiqs (JAX, GPU), QuantumOptics.jl |
Lindblad dynamics sector by sector, jumps between sectors, batches of Hamiltonians; no adaptive step size and no quantum trajectories |
| exact diagonalisation (9.7) | QuSpin, XDiag, SPINPACK, HΦ | the chain as an algebra, so states, channels and entropies apply per sector; SU(2) blocks; batched disorder; dense to L ≈ 16, Krylov to L ≈ 24 |
| symmetric tensor networks (0.3) | ITensor, TeNPy, TensorKit.jl, YASTN | no tensor networks; instead traces, states, conditional expectations and Haar measures of the algebra |
| quantum channels (6.6) | Qiskit quantum_info, toqito, QETLAB, QuTiP |
Kraus operators stored per sector and in batches, maps between different algebras, duals for each trace |
| random unitaries (5.5) | scipy.stats.unitary_group, QuTiP rand_unitary |
batched Haar sampling on the product of unitary groups U(k_1) × … × U(k_C), on GPU |
| representations, Wedderburn decomposition (4.8) | GAP, SageMath, RepLAB (MATLAB), SymbolicWedderburn.jl | a dense numerical decomposition with a certificate, returned as an algebra to compute in |
| equivariant neural networks (12.5) | escnn, e3nn, EMLP | the commutant as a *-algebra ⊕ M_{m_c}: products, positivity and functional calculus blockwise and differentiable; dense, small n only |
Features in detail¶
- Random operators with any spectrum –
operator_from_eigenvalues(sampler, ...)buildsA_c = U_c diag(λ) U_c*from a user callablesampler(dim) -> (dim,) or (batch, dim). - Unitary ensembles – Haar on U(n)/O(n), SU(n)/SO(n), COE, CSE, random diagonal phases;
batched (
random_unitary(n, measure, batch_size)), and block-diagonal unitaries in the algebra (random_unitary_operator). - Lazy evaluation –
X @ Y,X + Y,X * sand functional calculus build a recipe that is computed when.matrix(or a scalar functional) is requested; methods that need a property of the base operator first (e.g.X.inv,X.sqrt()checking positivity) evaluate the base. - Functional calculus at four levels (module
functional) –apply_function(f)for self-adjoint and for normal operators (unitaries; via the Hermitian eigensolver of H + αK),abs,inverse(pseudo-inverse with a relative cutoff),polar, norms and singular values (on GPUs for blocks > 32 via the Hermitian eigensolver of [[0, A], [A*, 0]], since batched SVD stops at 32),holomorphic_function(f)for any operator (contour integral, batched LU solves, error estimate);sqrt,log,power,expm,entropy;eigenvalues()without eigenvectors. Gradients are finite at degenerate spectra (Daleckii–Krein divided differences). Opt-in cache of the eigendecomposition:A.cache_spectrum()orwith torch_vn_algebra.caching(): .... - Three trace functionals –
Tr_blunt(Σ_c Tr A_c),Tr_norm(Σ_c Tr A_c / k_c) and the tracial statetau_vN((1/C) Σ_c Tr A_c / k_c); normstrace_norm,frobenius_norm,operator_norm;michelson_contrast. - Hilbert space utilities –
HilbertSpacewith inner products, orthonormal bases (standard / random / Haar), embedding and restriction of vectors and operators. - Inclusions of algebras (module
inclusions) –Inclusion(N, M, Lambda)for N ⊂ M given by an inclusion matrix (and an optional batched unitary): the trace-preserving conditional expectation for any faithful trace, Markov trace and index ‖Λ‖², Watatani index, Pimsner–Popa constants, basic construction, Jones projection and tower, relative commutant, Bratteli diagrams, and the U(1) spin-chain tower A_L ⊂ A_{L+1} (index 4cos²(π/(2L+4))). - Wedderburn decomposition (module
decompose) – the von Neumann algebra generated by given matrices or by a finite group, V*AV = ⊕c M, by the randomised algorithm of Murota et al. (2010), verified before it is returned; maps between n×n matrices and operators of ⊕} ⊗ 1_{m_cc M and of the commutant ⊕c M. - SU(2) symmetry of spin-1/2 chains (module
su2) –SU2Chain: the algebra ⊕S M of SU(2)-invariant operators, orthonormal highest-weight (Yamanouchi) bases in the U(1) sectors ofSpinChain, Heisenberg Hamiltonians with J₂ and fields, full spectra with quantum numbers, exact Clebsch–Gordan coefficients, Wigner–Eckart reduced matrix elements and spin correlations (L = 14: largest block 1001 instead of 3432). - Quantum Rényi divergences (module
divergences) – Petz and sandwiched Rényi divergences with exact support conventions, Umegaki, D_max and D_0 as limits, per-sector decomposition, Petz, rotated and universal recovery maps, noncommutative L^p and Kosaki norms; batched and differentiable. - Jordan algebras (module
jordan) – Jordan product, multiplication operator, quadratic representation P(y)x = yxy, Jordan triple product and powers, the transpose as a Jordan automorphism, the Jordan algebra generated by Hermitian matrices (jordan_algebra_generated, compared with the *-algebra they generate), spin factors V_m and the Lorentz cone (SpinFactor). - Permutation-invariant ensembles (module
permutational) – N two-level systems in the Schur–Weyl algebra ⊕j M: collective spin operators, Dicke and coherent states, local emission, pumping and dephasing as maps between spin sectors (} ⊗ 1_{m_jlocal_jump, anInterSectorChannel), and a batched banded Lindbladian with collective and local terms (PermutationalLindbladian, O(N³) per step; checked against the full 2^N space and QuTiP's PIQS).
Installation¶
Requirements: Python ≥ 3.8, PyTorch ≥ 1.13, NumPy ≥ 1.21 (the whole test suite runs in CI with exactly these
minimal versions and with the latest ones on Python 3.8–3.13); SciPy ≥ 1.7 only for the tests and scripts. On
Python 3.8 install the wheel from PyPI (pip install torch_vn_algebra); building from source needs Python ≥ 3.9.
From PyPI (install PyTorch for your CPU/GPU first, see https://pytorch.org):
pip install torch_vn_algebra
From the repository (needed for the scripts, tests and the paper):
git clone https://github.com/andre-a-no/von_neumann_type_i.git
cd von_neumann_type_i
pip install -e . # library only (torch, numpy)
pip install -e ".[scripts]" # + scipy, pandas, tqdm, matplotlib, seaborn, mpmath for scripts/
pip install -e ".[test]" # + pytest
Quick start¶
import torch
from torch_vn_algebra import TypeIAlgebra
device = 'cuda' if torch.cuda.is_available() else 'cpu'
# M = M_4(C) (+) M_6(C), acting on C^4 (+) C^6
alg = TypeIAlgebra(n_factors=[4, 6], k_factors=[4, 6], device=device)
# 100 random positive operators: eigenvalues ~ U[0, 1], Haar-random eigenvectors per block
X = alg.operator_from_eigenvalues(lambda dim: torch.rand(100, dim, device=device),
batch_size=100, force_positive=True, force_self_adjoint=True)
print(X.Tr_blunt().real.mean()) # sum of block traces
print(X.Tr_norm().real.mean()) # sum of normalised block traces
print(X.tau_vN().real.mean()) # tracial state (1/C) sum_c Tr(A_c)/k_c (complex in general)
print(X.michelson_contrast.mean()) # (lmax - lmin)/(lmax + lmin), shape (100,)
# operations stay lazy until .matrix or a scalar functional is requested
U = alg.random_unitary_operator(batch_size=100) # block-diagonal Haar unitary
Z = X.sqrt() @ U @ X.sqrt()
print(Z.trace_norm().mean(), X.entropy().mean())
Channels, states and dynamics¶
import torch
from torch_vn_algebra import TypeIAlgebra, channels, dynamics, states
alg = TypeIAlgebra([4, 4], [4, 4], complex_valued=True) # two superselection sectors
rho = states.random_density_matrix(alg, batch_size=100) # HS-random states on M
H = alg.operator_from_eigenvalues(lambda d: torch.randn(100, d), batch_size=100,
force_self_adjoint=True)
Phi = channels.random_channel(alg, kraus_rank=2, batch_size=100) # Haar Stinespring isometry
print(Phi.is_trace_preserving(), Phi.is_unital())
print(states.relative_entropy(Phi(rho), Phi(states.gibbs_state(H, 1.0))))
psi0 = torch.zeros(100, 2, 4, dtype=torch.complex64) # state vectors, block layout
psi0[:, 0, 0] = 1.0
psi_t = dynamics.schrodinger(psi0, H, times=[0.0, 1.0, 2.0]) # exact, shape (3, 100, 2, 4)
N = alg.from_blocks([torch.diag(torch.arange(4.0))] * 2) # dephasing jump operator
rho_t = dynamics.lindblad_evolve(rho, H, jumps=[N], times=[0, 1, 2]) # RK4, list of states
Lt = dynamics.lindblad_channel(alg, H, jumps=[N], t=2.0) # exact exp(tL) as a Channel
print((Lt(rho) - rho_t[-1]).frobenius_norm().max())
| Module | Contents |
|---|---|
Operator |
apply_function(f) (self-adjoint and normal), holomorphic_function(f) (any operator), polar, expm, log, power, eigh, eigenvalues (no eigenvectors, faster), cache_spectrum; alg.operator(tensor), alg.from_blocks([...]) |
channels |
Channel (Kraus form: apply, adjoint = Heisenberg picture, composition @, mix, choi, superoperator, from_superoperator, TP / unitality checks); identity, unitary, dephasing, depolarizing, amplitude damping, Lüders measurement, conditional expectation onto the centre, random (Stinespring) and random mixed-unitary channels |
states |
DensityMatrix (an Operator subclass: positive, unit Tr_blunt; expectation, mix, condition_on, density(trace) / from_density(..., trace) for the Tr_norm and tau_vN conventions; preserved by trace-preserving channels and by the dynamics), random density matrices (Hilbert–Schmidt, Bures, fixed rank), Gibbs states and partition functions, tracial state, sector probabilities, Born probabilities, Lüders update, entropy, relative entropy, fidelity, trace distance, purity |
channels.InterSectorChannel |
CP maps between sectors and between different algebras, Φ(ρ)_d = Σ_c Σ_i K_i^{dc} ρ_c K_i^{dc*}: duals for each of the three traces, composition, sector transition matrix, from_blocks, random_inter_sector_channel |
composite |
tensor_product(alg1, alg2) (sectors = pairs (c, d)), fused_tensor_product(alg1, alg2, fuse=add) (pairs with equal total charge merged into one sector), kron(A, B, alg12), partial_trace and partial_trace_channel (an InterSectorChannel; its dual is the embedding A ↦ A ⊗ 1) |
optimize |
constrained, batched multistart optimisation: UnitaryParam, PositiveParam / SelfAdjointParam with a prescribed or bounded Michelson contrast (hard constraint), extremize |
cost |
safeguards: ETA warnings for large batched decompositions and integrators, memory checks with InsufficientMemoryError, set_limits, disabled() |
chains |
SpinChain: spin-1/2 chains with conserved S^z, sectors N = 0..L (basis = iterated fused products), XXZ Hamiltonians with batched disorder, full algebra or single sectors, sparse form (xxz_sparse), momentum sectors of rings (xxz_momentum), reduced states and entanglement, site amplitude damping and jump operators lowering(i) between sectors, level-spacing ratio |
krylov |
SparseSectorHamiltonian (batch of diagonals + shared sparse hopping), batched Lanczos ground_state, Krylov evolve (exp(-itH) psi with adaptive steps and a proved error bound, krylov_error_bound); with SpinChain.xxz_sparse for sectors beyond dense matrices |
dynamics |
(Lindblad jumps may be InterSectorChannels, e.g. particle loss) exact propagator, schrodinger, von_neumann; RK4 schrodinger_rk4 (time-dependent H), solve_operator_ode (any dX/dt = f(t, X) in M), Lindblad lindblad_evolve (method="rk4" default, or the CPTP "kraus" and "exact" steps), lindblad_superoperator, lindblad_channel |
inclusions |
Inclusion (inclusion matrix Λ, optional unitary): embed, as_channel, conditional_expectation(trace) (ConditionalExpectation: Kraus form, index (Watatani), quasi_basis, pimsner_popa_constant(complete=...)), index (‖Λ‖²), markov_trace, basic_construction, jones_projection, jones_tower, relative_commutant, compose; bratteli_diagram, from_tensor_product, diagonal, scalars, centre, chain_algebra, chain_tower |
decompose |
wedderburn, from_group (all elements of a finite group), from_generators (any matrices, e.g. Lie algebra generators) → Decomposition (algebra, commutant_algebra, to_algebra / from_algebra, to_commutant / from_commutant, conditional_expectation, central_projections, verify); commutant_basis, twirl, permutation_representation, total_spin |
su2 |
SU2Chain: multiplicities, hw_basis, multiplet_basis, embed, heisenberg (J, J₂, field, arbitrary bonds; one spin without padding via spin=), restrict / lift (U(1) ↔ SU(2)), full_spectrum, lowest_energies, ground_state, reduced_matrix_elements, spin_correlation; clebsch_gordan, clebsch_gordan_exact, wigner_eckart, multiplicity |
divergences |
petz_renyi_divergence, sandwiched_renyi_divergence, renyi_divergence (with the limits α = 0, 1, ∞), umegaki_relative_entropy, max_relative_entropy, min_relative_entropy, sector_contributions; PetzRecovery / petz_recovery (also rotated), universal_recovery; lp_norm, kosaki_norm |
jordan |
jordan_product, jordan_multiplication, quadratic_representation, jordan_triple, jordan_power, transpose_map; jordan_algebra_generated, star_algebra_self_adjoint_part, is_jordan_subalgebra, compare_with_star_algebra → JordanAlgebra; SpinFactor, anticommuting_symmetries |
Inclusions, symmetries and divergences¶
import torch
from torch_vn_algebra import TypeIAlgebra, SU2Chain, states
from torch_vn_algebra import inclusions as inc, decompose as dc, divergences as dv
# inclusion N = M_2 (+) C c M = M_4 (+) M_3 with inclusion matrix Lambda
N = TypeIAlgebra([2, 1], [2, 1], precision='double')
M = TypeIAlgebra([4, 3], [4, 3], precision='double')
I = inc.Inclusion(N, M, [[1, 2], [1, 1]])
E = I.conditional_expectation('markov') # the trace-preserving conditional expectation M -> N
y = M.operator(torch.randn(5, M.C, M.k_max, M.k_max, dtype=M.dtype))
print(E(y).algebra is N) # True: E(y) is an operator of N (batch 5)
print(I.index(), E.pimsner_popa_constant()) # [M : N] = ||Lambda||^2, Pimsner-Popa constant
print([round(T.index(), 6) for T in inc.chain_tower(4)]) # spin-chain tower A_L c A_{L+1}: 4 cos^2(pi/(2L+4))
# the algebra generated by a symmetry: S_3 permuting three qubits
G = dc.permutation_representation(3, 2) # (6, 8, 8): all group elements
D = dc.from_group(G)
print(D.k_factors, D.multiplicities) # (1, 2), (4, 2): pi(S_3)'' = M_1 (+) M_2
blocks = D.to_algebra(G) # the group elements as operators of D.algebra
# SU(2) blocks of the Heisenberg ring, and Renyi divergences of states on M
chain = SU2Chain(12, 'periodic', complex_valued=False)
print(chain.multiplicities, chain.ground_state(chain.heisenberg()).energy) # [132, 297, ...], -5.387390917
rho, sigma = states.random_density_matrix(M, 10), states.random_density_matrix(M, 10)
print(dv.sandwiched_renyi_divergence(rho, sigma, 2.0), dv.petz_renyi_divergence(rho, sigma, 0.5))
Lazy operators are evaluated once and cached; after that they drop their recipe, so the
intermediate results of an expression such as X @ U @ Y can be garbage-collected. Laziness
defers and skips work but does not fuse operations; where it matters the library uses direct
formulas instead (e.g. Tr(ρA) in O(k²) via states.trace_of_product).
Channels map each sector to itself, so the Heisenberg dual is the same for Tr_blunt, Tr_norm
and tau_vN, and Hamiltonian / Lindblad dynamics with generators in M conserve the sector
probabilities Tr rho_c. Unitary dynamics requires complex_valued=True.
Number field and precision are two independent switches of every algebra:
complex_valued (real / complex) and precision ('single' / 'double'), i.e. float32, float64,
complex64 or complex128; alg.like(complex_valued=..., precision=...) and op.cast(alg2) move between
them, alg.bytes_per_operator(B) reports the memory. Rules of thumb (details and measurements in the
paper, scripts/numerics.py):
| use | when |
|---|---|
| real | the problem is real: real symmetric spectra, ground states without complex couplings, GOE statistics, orthogonal conjugation. Real is exact there and halves memory. Note that it changes the model otherwise (O(n) instead of U(n), GOE instead of GUE) |
| complex | unitary dynamics e^{-iHt}, Lindblad with a Hamiltonian, Haar U(n) / CUE / CSE / SU(n), random pure states, broken time reversal |
| single | Monte Carlo statistics whose statistical error is far above 1e-6, exploratory optimisation |
| double | effects that are small compared with the operators: inequalities near equality, small gaps, entropies of nearly pure states, contrast near 1, exact-result checks, long RK4 runs. On consumer GPUs FP64 is 32-64x slower than FP32 |
complex_valued=False gives real algebras (orthogonal instead of unitary groups); complex input with a non-zero
imaginary part is rejected there. Each force_* flag (self_adjoint, positive, normal, invertible,
projection) tags the operator after checking it against the sampled eigenvalues. The batch_size argument of
TypeIAlgebra only sets the batch of its Hilbert-space bases; every factory takes its own batch_size.
More in examples/: random Hamiltonian with a parity symmetry, free additive
convolution, a trace inequality, unitary ensembles, a Zipf density matrix, decoherence inside
superselection sectors, the Michelson contrast under unital and non-unital channels, the
spectral form factor of GUE vs. Poisson Hamiltonians, inclusions and their indices (example_inclusions.py),
Schur–Weyl duality from the symmetries alone (example_decompose.py), SU(2) multiplets of the Heisenberg ring
(example_su2.py), Rényi divergences with data processing and recovery (example_divergences.py), and Jordan
algebras and spin factors (example_jordan.py).
Reproducing the paper¶
The paper (man/article/main.tex, compiled man/article/main.pdf) contains no number
typed by hand: every table and figure comes from the scripts in scripts and the listings in
man/article/listings, run by one driver:
bash scripts/run_all.sh # quick mode, about 4 minutes on a 4-thread CPU
bash scripts/run_all.sh full cuda # sizes of the paper, on a GPU
cd man/article && pdflatex main.tex && pdflatex main.tex
Running the full experiments on a GPU server¶
git clone https://github.com/andre-a-no/von_neumann_type_i && cd von_neumann_type_i
git checkout claude/funny-ptolemy-confo7 # until it is merged into main
python -m venv .venv && source .venv/bin/activate
pip install torch # the CUDA build matching the server's driver, see pytorch.org
pip install -e ".[scripts,test]"
python -m pytest -q # ~30 s, must pass
python scripts/gpu_check.py # ~10 min: correctness against the CPU, no per-sector work, timings
bash scripts/run_all.sh check cuda # full sizes, minimal repetitions: tests every code path and the GPU memory
nohup bash scripts/run_all.sh full cuda > full_run.log 2>&1 & # the full run (or inside tmux)
- Scripts and the parts of the paper they produce:
validate_known_results(Section 5),numerics(Section 3),inequality_searchandbounds_search(Section 6),renyi_threshold(Section 6.1: the data-processing thresholds of the sandwiched and Petz Rényi divergences, located by batched optimisation with violations certified in mpmath; about 2 minutes in quick mode on one CPU thread),connes_stormer(Section 6, use case: the Connes–Størmer relative entropy of two maximal abelian subalgebras of M_n, closed form for n = 2 and the observation that orthonormal bases are not always optimal for n ≥ 3; about 12 minutes in full mode on a CPU),chains(Section 7),baselines(Section 9),benchmark_library(Section 10),permutational(use case: a boundary time crystal of N two-level systems under local emission and dephasing, N up to 80 on a CPU and 200 on a GPU),trace_characterizations(use case: how strongly inequalities that characterise the trace fail for Tr(W·) as a function of the Michelson contrast of W; about 5 minutes on a CPU),symmetry_resolved(use case: symmetry-resolved entanglement of XXZ ground states, ρ_A in ⊕q M without forming |ψ⟩⟨ψ|; L = 24 in about 20 minutes on a CPU). - The driver first prints the GPU, its memory, CUDA and cuDNN versions (also saved to
man/article/generated/logs/environment.txt) and stops if CUDA is not available. - Every script writes its own log to
man/article/generated/logs/<script>.log, including run-time estimates from thecostmodule. A failing script does not stop the others; the run ends with a summary. ONLY="chains baselines" bash scripts/run_all.sh full cudareruns a subset.scripts/gpu_check.py(about 10 minutes, most of it the CPU side of the timings) has three parts: (1) correctness: the same inputs on the CPU and the GPU, all operations of the paper, single and double precision, Haar sampling (Gram-Schmidt on the GPU against QR, moments); (2) structure: host synchronisations and kernel launches per operation must not grow with the number of sectors (C = 1, 16, 256 at a fixed total size); (3) timings against all CPU cores, with the verdict on the hypothesis "the GPU is faster for large batches" and a comparison of the batched generation with the per-sector algorithm of the first full run. It exits with an error if (1) or (2) fails, and writesgpu_check.json. Run it first on a new machine or after changing the library.tests/test_sync_free.pychecks the absence of host reads on the CPU, in the normal test suite.- Duration. On an RTX 5090 (16-thread CPU) the first full run took about 5 hours: validate 12 min, numerics and
baselines 3 min, benchmark 8 min, chains 7 min, inequality_search 3.4 h, bounds_search 1.3 h; most of
inequality_search was Haar sampling with a per-matrix QR on the GPU, now batched. On a 4-thread CPU
the
checkrun takes about 33 minutes (validate 90 s, numerics 88 s, baselines 45 s, benchmark 189 s, chains 623 s, inequality_search 49 s, bounds_search 871 s; every log ends with a[resources]line). - Interruptions.
inequality_searchandbounds_searchsave every finished part togenerated/partial/; after a crash or reboot, rerunning the same command continues where it stopped and gives the same numbers. - Several GPUs. The scripts are independent, e.g.
CUDA_VISIBLE_DEVICES=0 ONLY=bounds_search bash scripts/run_all.sh full cudaandCUDA_VISIBLE_DEVICES=1 ONLY="validate_known_results numerics baselines benchmark_library chains inequality_search" bash scripts/run_all.sh full cudain two terminals (the examples run only whenONLYis not set). ... full cuda tf32repeats the run with TF32 tensor-core matrix products (Ampere or newer), to measure their effect; the default is true FP32.- Results land in
man/article/generated/(tables as.texand.json) andman/article/figures/. Commit those two directories and the logs, then rebuild the PDF; the tables state the device and mode they came from. - Memory. The largest single step is the dense diagonalisation of a 12870-dimensional sector in
chains(about 10 GB on the CPU in thecheckrun); use a GPU with at least 24 GB, preferably 40-80 GB, and a host with at least 16 GB RAM. Thecheckrun shows the peak GPU memory of every script in its[resources]line. - Double precision is used throughout the validation and chain scripts; a data-centre GPU (A100, H100) is much faster there than a consumer GPU (FP64 at 1/2 versus 1/32-1/64 of the FP32 rate).
Repository layout¶
torch_vn_algebra/ library: algebra.py (TypeIAlgebra, Operator), hilbert_space.py, channels.py,
states.py, dynamics.py, composite.py, chains.py, krylov.py, optimize.py, cost.py,
functional.py, inclusions.py, decompose.py, su2.py, divergences.py,
jordan.py
tests/ pytest suite (CPU, runs in CI)
examples/ short usage examples of the library
man/ the documentation:
lecture_notes/ the theory: definitions, theorems, complete proofs (7 languages)
tutorial/ the library in the context of the theory, executed by the tests (7 languages)
article/ the paper: main.tex, main.pdf, generated/ (tables), figures/, listings/ (its programs)
scripts/ run_all.sh and the scripts behind every table and figure of the paper
Tests¶
pytest -q
Contributing¶
Bug reports, questions and pull requests are welcome: see CONTRIBUTING.md and the Code of Conduct. Changes between versions are listed in CHANGELOG.md.
Limitations¶
- SVD dominates the cost for
k_max ≳ 200with large batches. SpinChain(2, 'periodic')has a single bond (the wrap bond is not counted twice), so it equals the open chain.Channel, Hamiltonians and Lindblad generators preserve the sectors; maps between sectors areInterSectorChannels. Only abelian charges are fused. Of the non-abelian symmetries, SU(2) reduces the blocks of spin-1/2 chains (SU2Chain, dense up to L ≈ 16); SU(2) combined with momentum and chains of larger spins are not implemented.decomposehandles any symmetry given by matrices, but densely (n up to a few hundred) and over C only. Operators on all sectors of a chain are stored padded (work in single sectors for large chains).- Sandwiched Rényi divergences for small α < 1 of ill-conditioned states are not accurate (the sandwich σ^g ρ σ^g, g = (1-α)/(2α), cannot be resolved in floating point).
lindblad_channel,choiandsuperoperatorwork with k_c² × k_c² matrices per block, which limits them to blocks of a few tens; uselindblad_evolvefor larger blocks.- Gradients of the functional calculus are finite at degenerate spectra; gradients through the eigenvectors
returned by
eighare not. Second derivatives of f(A) are correct at non-degenerate spectra. holomorphic_functionneeds a circle that encloses the spectrum and on which f is holomorphic (the default circle suits entire functions such as exp); for self-adjoint or normal operatorsapply_functionis exact and cheaper.- On GPUs, batched Hermitian eigensolvers cover all block sizes only in recent PyTorch/cuSOLVER releases.
- Finite dimensions and Type I only.
Citation¶
Please cite the paper and, for reproducibility, the version of the software you used (metadata in CITATION.cff; GitHub shows a "Cite this repository" button; every release is archived with a DOI on Zenodo).
@misc{nikolaeva_novikov_2026_torch_vn_algebra,
title = {Finite-Dimensional Type I von Neumann Algebras in PyTorch:
A GPU-Accelerated Framework for Random Block-Diagonal Operators},
author = {Nikolaeva, Irina and Novikov, Andrej},
year = {2026},
eprint = {2606.15882},
archivePrefix = {arXiv},
primaryClass = {cs.MS},
url = {https://arxiv.org/abs/2606.15882}
}
@software{torch_vn_algebra,
title = {torch\_vn\_algebra: block-diagonal operators, symmetry sectors and quantum channels in {PyTorch}},
author = {Nikolaeva, Irina and Novikov, Andrej},
year = {2026},
version = {1.0.0},
url = {https://github.com/andre-a-no/von_neumann_type_i}
}
License¶
MIT, see LICENSE.