Add a TensorKit backend extension#197
Conversation
Codecov Report❌ Patch coverage is
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
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
dac51e5 to
95f7c67
Compare
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.
e3cc883 to
4e87ecc
Compare
lkdvos
left a comment
There was a problem hiding this comment.
Overall looks good to me! I have one comment about the subtlety in unmatricize, otherwise should be good to go!
| "`unmatricize` axes `$(dest_codomain ← dest_domain)` do not fuse to `$(space(m))`" | ||
| ) | ||
| ) | ||
| return TensorMap{scalartype(m)}(m.data, dest_codomain ← dest_domain) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
"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`.
|
@lkdvos ready to merge? |
## 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>
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
AbstractArrayvocabulary. This PR makesndimsandaxesTensorAlgebra-owned functions that forward toBaseby default, so aTensorMappresents its index spaces as its "axes" by overloading them while leavingBaseuntouched. The real work routes to TensorKit's existing TensorOperations interface, which already implements contraction and permuted addition forTensorMaps.matricizeregroups aTensorMap's indices into the requested codomain/domain bipartition with a singlepermute.unmatricizeis its inverse: since aTensorMapalready is the linear map its space describes, it checks that the requested codomain/domain split matchesm's own space and returns it unchanged.