Nicole 0.3.2
Nicole 0.3.2 - Einstein Summation and Tensor Serialization
Release Date: April 7, 2026
Version 0.3.2 introduces two significant new capabilities: einsum brings Einstein summation notation to symmetry-aware tensors, and serialize/deserialize enable portable, torch.save-compatible persistence of Tensor instances including their SU(2) intertwiner weights. The release also cleans up several API inconsistencies — most notably the simplification of transpose, the standardization of block-identifier terminology, the refactoring of compression logic into BlockSchema.block_compress, and the removal of the now-redundant Tensor.compress.
✨ New Features
einsum — Einstein Summation Notation
A new einsum(equation, *tensors) function (module nicole/einsum.py) parses a subscript equation string and dispatches to contract, trace, and permute to carry out the requested operation.
Three equation forms are supported:
- Permutation — single input tensor with a reordered output subscript, e.g.
'ij->ji' - Trace — single input tensor with one repeated subscript letter, e.g.
'ii->'; surviving axes are permuted as needed - Sequential contraction — two or more tensors contracted left-to-right, e.g.
'ij,jk->ik'; tensors are contracted pairwise in order (no contraction-order optimization)
einsum supports both Abelian and SU(2) symmetry groups and is exposed as a top-level public API function.
serialize / deserialize — Tensor Persistence
A new serialize.py module provides serialize(tensor) and deserialize(payload, device=None) for lossless round-trip conversion of Tensor instances to and from plain Python dicts.
The serialized payload:
- Uses only Python primitives (
str,int,tuple,dict,None) andtorch.Tensorvalues - Is directly compatible with
torch.save/torch.load(..., weights_only=True) - Encodes the full block-sparse data structure, index metadata (direction, symmetry group, sectors), and SU(2) intertwiner weights
- Carries a
"version"key for forward-compatibility
Bridge serialization is also supported internally (delegated through serialize.py). Both serialize and deserialize are exposed as top-level public API functions.
BlockSchema.block_compress — Automatic Component Compression
A new static method BlockSchema.block_compress(data, bridge, cutoff=1e-14) removes linearly dependent components from a single non-Abelian tensor block via thin SVD on the Bridge weight matrix. Singular vectors whose singular values fall below cutoff are discarded; the retained factors are absorbed back into the reduced data so that the physical block R @ W is exactly preserved.
block_compress is now called automatically by:
Tensor.regularize— after the normalization pass (cutoff forwarded via a newcutoffkwarg)Tensor.__add__andTensor.__sub__— per block afterblock_add, to reclaim rank deficiencies that arise when operands carry identical or collinear componentsoplus— same treatment as addition and subtractioncontractandtrace— via the now unconditionalregularize()call that replaces the previous conditionalcompress()
🔧 API Changes
transpose Signature Simplified
transpose no longer accepts a positional *order argument. It now unconditionally reverses all tensor axes — the only sensible all-axes transposition. Callers that previously passed an explicit axis ordering should use permute instead.
This change applies to both maneuver.transpose(tensor) and Tensor.transpose(in_place=False).
Tensor.compress Removed
Tensor.compress() has been removed from the public API. Its logic now lives in BlockSchema.block_compress and is invoked automatically through regularize. Callers that previously called .compress() explicitly should call .regularize() instead, which now covers both normalization and compression.
block_ids Terminology
Block-identifier parameter names have been standardized across the display layer:
tensor_summary:block_numbersrenamed toblock_idsTensor.show:block_indicesrenamed toblock_ids
📖 Documentation
New API Pages
einsumAPI page: Full reference with equation syntax, supported forms, and worked examples for Abelian and SU(2) tensorsserializeAPI page: Reference documentation with the full serialized dict schema andtorch.save/torch.loadusage examplesdeserializeAPI page: Reference documentation including thedeviceargument and version-error behavior
Examples
- Serialization examples: New advanced examples page demonstrating round-trip persistence with
torch.saveandtorch.load - Contraction examples: Extended with
einsumequation examples covering permutation, trace, and multi-tensor contractions
Other Updates
transposedocumentation: Updated to reflect the removedorderargument and the new always-reverse semanticsidentitydocumentation: Corrected namespace reference and removed outdated notes- Display documentation: Enhanced with additional details on Nicole's tensor summary format
- SU(2) Protocol page: Added a references section
- Examples index: New entries for Autograd, GPU Acceleration, and Serialization
🧪 Test Suite (1521 tests)
- 1521 tests pass, 10 skipped (accelerator-only tests on CPU-only CI)
- New test module:
tests/operations/test_einsum.py(39 tests) covering permutation, trace, and sequential contraction for Abelian and SU(2) tensors, including higher-order multi-tensor equations - Serialization tests added to
tests/support/test_helpers.py(11 new tests): round-trip for U(1), Z2, product-Abelian, SU(2), and product-SU(2) tensors; complex dtype; scalar tensors;torch.save/torch.loadintegration;deviceforwarding; unknown-version error - Bridge serialization tests added to
tests/symmetry/test_delegate.py(7 new tests) - Outer product consistency tests added to
tests/integration/test_consistency.py(8 new tests) for U(1) and SU(2) groups tests/operations/test_maneuver.py: transpose tests rewritten to reflect the simplified signaturetests/primary/test_blocks.py: five new tests forblock_compresscovering single-component no-op, independent rows unchanged, dependent rows reduced, cutoff sensitivity, and input immutabilitytests/support/test_helpers.py: four new tests for the compression aspect ofTensor.regularize; existing tests updated to use physical-tensor comparison where SVD sign ambiguity would break strict weight equalitytests/operations/test_arithmetic.pyandtest_oplus.py: assertions updated to compare physical contentR @ Wrather than raw component counts; oplus weight tests upgraded to 4-index spin-1 tensors (om_dim = 3) where component count assertions remain meaningful
📊 Statistics
Code Changes
- 65 commits since v0.3.1
- 46 files changed: 3,187 insertions, 705 deletions
- New source modules:
src/nicole/einsum.py,src/nicole/serialize.py - Source modules touched:
tensor.py,blocks.py,contract.py,display.py,maneuver.py,symmetry/delegate.py,__init__.py - New test module:
tests/operations/test_einsum.py
✅ Compatibility
Breaking Changes:
transpose(tensor, *order)/Tensor.transpose(*order, in_place)— theorderargument is removed; usepermutefor custom axis orderings.tensor_summary(..., block_numbers=...)→tensor_summary(..., block_ids=...)Tensor.show(block_indices=...)→Tensor.show(block_ids=...)Tensor.compress()— removed; useTensor.regularize()instead, which now covers both normalization and compression.
All other changes are backward compatible with v0.3.1.
Requirements:
- Python ≥ 3.11
- PyTorch ≥ 2.5
- Yuzuha ≥ 0.1.5
🙏 Notes
Version 0.3.2 rounds out the contraction API with einsum — a familiar notation for users coming from NumPy or PyTorch — while making tensor checkpointing straightforward via serialize/deserialize. The accompanying API clean-up eliminates accumulated inconsistencies and sets a cleaner baseline for future releases.