Skip to content

Add a TensorKit backend extension#197

Merged
mtfishman merged 4 commits into
mainfrom
mf/tensorkit-ext
Jul 2, 2026
Merged

Add a TensorKit backend extension#197
mtfishman merged 4 commits into
mainfrom
mf/tensorkit-ext

Conversation

@mtfishman

@mtfishman mtfishman commented Jul 1, 2026

Copy link
Copy Markdown
Member

Summary

Adds a TensorKit extension so TensorMaps flow through TensorAlgebra's generic tensor operations (contract, matricize/unmatricize, and permuted addition) unchanged.

TensorAlgebra's generic code describes operands in an AbstractArray vocabulary. This PR makes ndims and axes TensorAlgebra-owned functions that forward to Base by default, so a TensorMap presents its index spaces as its "axes" by overloading them while leaving Base untouched. The real work routes to TensorKit's existing TensorOperations interface, which already implements contraction and permuted addition for TensorMaps.

matricize regroups a TensorMap's indices into the requested codomain/domain bipartition with a single permute. unmatricize is its inverse: since a TensorMap already is the linear map its space describes, it checks that the requested codomain/domain split matches m's own space and returns it unchanged.

@mtfishman mtfishman changed the title Add a TensorKit backend extension [WIP] Add a TensorKit backend extension Jul 1, 2026
@mtfishman mtfishman marked this pull request as draft July 1, 2026 21:00
@codecov

codecov Bot commented Jul 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.77778% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 83.11%. Comparing base (4a91f95) to head (ede1295).

Files with missing lines Patch % Lines
ext/TensorAlgebraTensorKitExt.jl 75.00% 6 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #197      +/-   ##
==========================================
+ Coverage   83.03%   83.11%   +0.08%     
==========================================
  Files          22       24       +2     
  Lines         672      699      +27     
==========================================
+ Hits          558      581      +23     
- Misses        114      118       +4     
Flag Coverage Δ
docs 26.48% <11.53%> (-0.61%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mtfishman mtfishman force-pushed the mf/tensorkit-ext branch 2 times, most recently from dac51e5 to 95f7c67 Compare July 1, 2026 22:03
@mtfishman mtfishman changed the title [WIP] Add a TensorKit backend extension Add a TensorKit backend extension Jul 1, 2026
@mtfishman mtfishman marked this pull request as ready for review July 1, 2026 22:05
@mtfishman mtfishman requested a review from lkdvos July 1, 2026 22:05
mtfishman added 3 commits July 1, 2026 22:24
Adds a TensorKit extension so `TensorMap`s flow through TensorAlgebra's generic tensor operations (`contract`, `matricize`/`unmatricize`, and permuted addition) unchanged.

TensorAlgebra's generic code describes operands in an `AbstractArray` vocabulary. This PR makes `ndims` and `axes` TensorAlgebra-owned functions that forward to `Base` by default, so a `TensorMap` presents its index spaces as its "axes" by overloading them while leaving `Base` untouched. The real work routes to TensorKit's existing TensorOperations interface, which already implements contraction and permuted addition for `TensorMap`s.

`matricize` regroups a `TensorMap`'s indices into the requested codomain/domain bipartition with a single `permute`. `unmatricize` reconstructs the axes: when they match the matrix index-for-index it returns it unchanged, and when they fuse to the matrix's codomain/domain (an ITensor-style combiner split) it rewraps the block data on the new spaces with no copy, since a fused split is a reshape rather than a basis change.
## Summary

`similar_map` built its codomain and domain product spaces by splatting into `ProductSpace(...)`, which throws `MethodError: no method matching ProductSpace()` when either axis tuple is empty. An all-codomain `TensorMap` (empty domain) is how ITensorBase direct-wraps a `TensorMap`, so this path has to work. Build with the parametric `ProductSpace{S}(...)` form instead, which accepts an empty argument list and yields the trivial space, and cover both empty-side cases with a regression test.
`unmatricize` now takes its domain axes codomain-facing (un-dualized), so the
backend builds `dest_domain` directly from them instead of re-dualizing, and the
round-trip tests pass the domain spaces un-dualized.

@lkdvos lkdvos left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks good to me! I have one comment about the subtlety in unmatricize, otherwise should be good to go!

Comment thread ext/TensorAlgebraTensorKitExt.jl Outdated
"`unmatricize` axes `$(dest_codomain ← dest_domain)` do not fuse to `$(space(m))`"
)
)
return TensorMap{scalartype(m)}(m.data, dest_codomain ← dest_domain)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly subtle when including symmetries - I do think it works because we sorted our data to be compatible with this, but it is a very specific choice of "combiner" that is not necessarily the same as multiplying with an identity.

Where are we using this? I'm having a hard time gauging if it can lead to issues.
In any case, this heavily specializes on m::TensorMap, since not all AbstractTensorMap have the data field (nor that layout, e.g. AdjointTensorMap or DiagonalTensorMap)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this one I was least sure about, my initial definition required that the input and output space match as a "safer" version. One application of this I have in mind is combining/fusing some subset of legs (i.e. really reducing the tensor from N legs to N - f legs), which in ITensor we use the combiner for but I wanted to implement that in the new system through primitives of matricization/unmatricization. But I can definitely see there may be issues with choices of bases, etc. I think most of what we need right now is the stricter version where the spaces match, which is a no-op on a TensorMap, so I can go back to that and think a bit more about the code pattern... Would it be safer/more generic to use https://quantumkithub.github.io/TensorKit.jl/stable/lib/tensors/#TensorKit.isomorphism? I was trying to avoid that since I didn't want to actually do the multiplication if possible.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, for now I turned it into a stricter version that checks the spaces are the same and if so just returns the input, and we can consider generalizations later.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the main problem is just that even though this is a specific choice of combiner/isomorphism, it is hard to guarantee that the correct inverse isomorphism will be used on the other end. With dense arrays it is somewhat less problematic because choosing reshape as the specific isomorphism is usually the best option, but simply reinterpreting the data really boils down to a non-trivial isomorphism that depends on the layout and sorting, and I think in general it doesn't match with the isomorphism you'd get from isomorphism(spaces, fuse(spaces)), which is multiplying with an identity. (It only matches if you are fusing all legs of the domain/codomain).

In TensorKit we more or less punt on that by making people create the isomorphisms explicitly, and I don't think I've had this be an issue before because that operation really isn't very common anyways, the primary reason you tend to want to do this is for factorizations etc, for which we don't actually need to combine the legs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"It only matches if you are fusing all legs of the domain/codomain": I think that's essentially the combiner use case we have in mind. But I agree most cases of the combiner could be done with some kind of factorization, and we'll probably move towards that anyway (it's just helpful to have some way to do what we do with our current combiner for upgrading code).

Restrict the extension's `unmatricize` to the case where the requested codomain/domain split matches the `TensorMap`'s own space, returning it unchanged and throwing otherwise. Dropping the combiner-style data rewrap also removes the dependence on the concrete `TensorMap` layout, so it works for any `AbstractTensorMap`.
@mtfishman

Copy link
Copy Markdown
Member Author

@lkdvos ready to merge?

@mtfishman mtfishman merged commit 46233e2 into main Jul 2, 2026
20 of 21 checks passed
@mtfishman mtfishman deleted the mf/tensorkit-ext branch July 2, 2026 15:01
mtfishman added a commit to ITensor/ITensorBase.jl that referenced this pull request Jul 2, 2026
## Summary

Lets a native TensorKit space be an `Index`, so an `ITensor` wraps a
TensorKit `TensorMap` directly as its parent with no `AbstractArray` in
between. `NamedTensor` and `NamedUnitRange`/`Index` are loosened to hold
a `TensorMap` and a native space, and the generic named-tensor machinery
(`nameddims`, named `getindex`, and the `copy`/`zero` helper) is
loosened to accept any parent a `NamedTensor` can wrap rather than only
an `AbstractArray`, so a `TensorMap` flows through with no
backend-specific path. Cold-start construction (`randn`/`rand`/`zeros`
over index spaces) and linear-combination broadcasting (`a .+ b`, `2 .*
a`) build on the map-shaped construction and broadcast hooks in
ITensor/TensorAlgebra.jl#200, which dispatch on
the axis type, so the TensorKit extension is left holding just the
space-backed `Index` adapters. Element-wise broadcasting on a symmetric
tensor has no meaning and errors cleanly. Contraction and factorization
work through the TensorAlgebra TensorKit backend
(ITensor/TensorAlgebra.jl#197) with no
additional code.

This is the direct route to non-abelian symmetries such as SU(2) through
the `ITensorBase` to `TensorAlgebra` to `TensorKit` path.

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants