Skip to content

Conversation

ogauthe
Copy link
Collaborator

@ogauthe ogauthe commented Aug 15, 2025

This PR fixes bipermutation inversion in contract. A given bipermutation alone does not carry enough information to be inverted: the bipartition of the output must be specified. It turns out both permutation are needed in contract stack at different times. I managed to pass only one inside each function and to invert it using information from other arguments.

The logic of the code is now correct and able to handle arrays with bipartitions. We need bipermutations for both directions and be explicit which is which. I would be happy to improve the names I used.

Note that BlockArrays tests fail due to JuliaArrays/BlockArrays.jl#295.

Copy link

codecov bot commented Aug 15, 2025

Codecov Report

❌ Patch coverage is 97.50000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.39%. Comparing base (3aaed6c) to head (6e8fd84).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/matricize.jl 95.83% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #75      +/-   ##
==========================================
- Coverage   94.90%   94.39%   -0.51%     
==========================================
  Files          14       14              
  Lines         451      464      +13     
==========================================
+ Hits          428      438      +10     
- Misses         23       26       +3     
Flag Coverage Δ
docs 0.00% <0.00%> (ø)

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

☔ View full report in Codecov by Sentry.
📢 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.

@lkdvos lkdvos mentioned this pull request Aug 27, 2025
@lkdvos
Copy link
Contributor

lkdvos commented Aug 27, 2025

I missed a part of the discussion but this also came up when writing the extension to TensorOperations, so let me perhaps elaborate slightly on what TensorOperations does, which could be used as inspiration of what (not) to do.

From the point of TensorOperations, we chose our permutations/inverses such that effectively a contraction for a dense array would be, if simplified:

function tensorcontract!(C, A, (p1A, p2A), B, (p1B, p2B), (p1AB, p2AB), ...)
    A_mat = reshape(permutedims(A, (p1A..., p2A...)), prod(i->size(A, i), p1A), prod(i -> size(A, i), p2A))
    B_mat = reshape(permutedims(B, (p1B..., p2B...)), prod(i -> size(B, i), p1B), prod(i -> size(B, i), p2B))
    AB = reshape(A_mat * B_mat, size.(Ref(A), p1A), size.(Ref(B), p2B))
    C += permutedims(AB, (p1AB..., p2AB...))
    return C
end

Importantly, the partition for pX = (p1X, p2X) is always chosen such that you could write permutedims(X, pX) and have enough information to know what the output of that would be.

Now, in order to avoid forming AB whenever this is not required, we basically have an if statement that detects when permutedims(C, inv_AB(pAB)) (with inv_AB a permutation inverse with partition of AB) results in a stridedview that is still compatible with BLAS, in which case we indeed do mul!(permutedims(C, inv_AB(pAB)), A_mat, B_mat, alfa, beta), otherwise we simply allocate a temporary AB = A_mat * B_mat and then do an in-place addition C .= beta .* C .+ alfa .* permutedims(AB, pAB...)

The main idea being that where we use alfa and beta never really affects performance, and indeed we require both the inverse and regular blocked permutation to have both fast paths.

I think I quite like the naming scheme (might just be habit) of having permX being the thing that corresponds to permutedims(X, permX), which would entail permutedims(AB, permAB) and permC = inv_AB(permAB) for C_ = permutedims(C, permC).
Additionally, using permAB as the argument instead of permC is a bit more convenient since the partition of AB can be inferred from permA and permB alone, while the partition of permAB can only be inferred from the output array C, which for regular arrays just does not store that information.

@ogauthe
Copy link
Collaborator Author

ogauthe commented Sep 2, 2025

@lkdvos indeed permAB is the most convenient one and the main point of this PR is replacing permC with permAB in most of the functions. However in unmatricize we end up needing both at some point.

We considered something similar to detecting a trivial inv_AB(pAB), however we concluded that we cannot assume matricize not to copy (it will always copy data for a GradedArray for instance). Therefore we cannot generically assume inplace operation and need to call unmatricize_add.

@lkdvos
Copy link
Contributor

lkdvos commented Sep 2, 2025

We considered something similar to detecting a trivial inv_AB(pAB), however we concluded that we cannot assume matricize not to copy (it will always copy data for a GradedArray for instance). Therefore we cannot generically assume inplace operation and need to call unmatricize_add.

Indeed, for symmetric tensors we actually only support the case where the final permutation is trivial. (Although with the caveat that you can sometimes alter pA and pB in order to trivialize pAB, and vice-versa, so there is an additional cost model associated to that)
The main example being: pA = ((1, 2), (3,)), pB = ((1,), (2,)) and pAB = ((2, 1), (3,)), where you can choose to prepermute A in order to avoid having to permute AB, for example when B is strongly rectangular one might be more efficient than the other.

@mtfishman
Copy link
Member

Looks good. Are the tests you marked as broken fixed by JuliaArrays/BlockArrays.jl#485?

@ogauthe
Copy link
Collaborator Author

ogauthe commented Sep 2, 2025

Looks good. Are the tests you marked as broken fixed by JuliaArrays/BlockArrays.jl#485?

Yes

I use both length_domain and length_codomain in FusionTensors, it makes more sense to define both here.

I also updated the checks in output_axes: our conventions are axes must be dual from each other. Since dual(ax) == ax is an implementation detail of GradedArrays, I think it is better to check the length only.

With these changes, I am now able to use the generic contract in FusionTensors.

Ready to merge.

@mtfishman mtfishman merged commit 981f5c0 into ITensor:main Sep 3, 2025
13 of 14 checks passed
@ogauthe ogauthe deleted the biperm_out2 branch September 3, 2025 12:49
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.

3 participants