Skip to content

Conversation

vneiger
Copy link
Contributor

@vneiger vneiger commented Sep 5, 2025

This computes the determinant for matrices over GF(2**e) by directly relying on the PLE decomposition of M4RIE. This provides a significant speed-up over the generic implementation used until now for this class.

Before:

sage: mat = matrix.random(GF(2**4), 200, 200)
sage: %time dd = mat.det()
CPU times: user 706 ms, sys: 2.26 ms, total: 708 ms
Wall time: 705 ms
sage: mat = matrix.random(GF(2**8), 200, 200)
sage: %time dd = mat.det()
CPU times: user 746 ms, sys: 890 µs, total: 747 ms
Wall time: 744 ms
sage: mat = matrix.random(GF(2**15), 200, 200)
sage: %time dd = mat.det()
CPU times: user 1.37 s, sys: 4.74 ms, total: 1.38 s
Wall time: 1.37 s
sage: mat = matrix.random(GF(2**4), 500, 500)
sage: %time dd = mat.det()
CPU times: user 10.8 s, sys: 23.8 ms, total: 10.8 s
Wall time: 10.7 s
sage: mat = matrix.random(GF(2**8), 500, 500)
sage: %time dd = mat.det()
CPU times: user 11.6 s, sys: 22.1 ms, total: 11.6 s
Wall time: 11.6 s
sage: mat = matrix.random(GF(2**15), 500, 500)
sage: %time dd = mat.det()
CPU times: user 21.7 s, sys: 37 ms, total: 21.7 s
Wall time: 21.7 s

After:

sage: mat = matrix.random(GF(2**4), 200, 200)
sage: %time dd = mat.det()
CPU times: user 275 µs, sys: 68 µs, total: 343 µs
Wall time: 356 µs
sage: mat = matrix.random(GF(2**8), 200, 200)
sage: %time dd = mat.det()
CPU times: user 1.1 ms, sys: 21 µs, total: 1.12 ms
Wall time: 1.13 ms
sage: mat = matrix.random(GF(2**15), 200, 200)
sage: %time dd = mat.det()
CPU times: user 81.2 ms, sys: 8.98 ms, total: 90.1 ms
Wall time: 89.7 ms
sage: mat = matrix.random(GF(2**4), 500, 500)
sage: %time dd = mat.det()
CPU times: user 1.84 ms, sys: 0 ns, total: 1.84 ms
Wall time: 1.86 ms
sage: mat = matrix.random(GF(2**8), 500, 500)
sage: %time dd = mat.det()
CPU times: user 5.23 ms, sys: 39 µs, total: 5.27 ms
Wall time: 5.44 ms
sage: mat = matrix.random(GF(2**15), 500, 500)
sage: %time dd = mat.det()
CPU times: user 719 ms, sys: 17.6 ms, total: 737 ms
Wall time: 732 ms

Copy link

github-actions bot commented Sep 5, 2025

Documentation preview for this PR (built with commit 8ee83ca; changes) is ready! 🎉
This preview will update shortly after each push to this PR.


sig_on()
cdef int r = mzed_ple(A, P, Q)
sig_off()
Copy link
Contributor

Choose a reason for hiding this comment

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

My impression is it would make the code much easier to maintain if you just make a single method that compute the PLE decomposition, then in determinant you return prod(self.ple_decomposition()[0].diagonal()) or something. If one can accept the overhead (which is likely negligible compared to the likely O(n^3) PLE decomposition).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I agree (including about the negligible overhead), however this requires a bit more work: already for doing it for this specific class, but also and mostly because I think there is some rewriting to do so that the general LU method calls this kind of fast PLE/PLUQ/... when available. For example matrices in modn_dense or mod2 or gf2e should be able to do LU by a direct call to FFLAS-FFPACK/M4RI/M4RIE functions for PLE/PLUQ. For the moment, Sage's LU is a naive algorithm for these classes. So I preferred to simply accelerate the determinant for now, and keep the LU improvements for a moment when I have time to do them properly.

Copy link
Contributor

@user202729 user202729 Sep 6, 2025

Choose a reason for hiding this comment

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

not necessarily so, you can just expose an additional method .ple_decomposition() and explain what it does. Making LU call it is an optional extra improvement.

But either way, this is optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To remember about this comment and handle it when time permits (if no one does it before then), I have created #40791


cdef Cache_base cache = <Cache_base> self._base_ring._cache

# characteristic 2, so det(P) == det(Q) == 1
Copy link
Contributor

Choose a reason for hiding this comment

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

PLE decomposition has P and Q being a permutation matrix in whatever characteristic right? (plus, here P and Q aren't really matrix either, they're represented by permutations which can be converted to permutation matrix if necessary)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, well in case of PLUQ yes, for PLE (which has no Q in its definition) it depends what we mean by Q and how pivots are chosen, but here for M4RIE both are indeed permutation matrices. The comment is here because if the characteristic was not 2, the determinant of P and Q would need to be computed.

vbraun pushed a commit to vbraun/sage that referenced this pull request Sep 11, 2025
sagemathgh-40773: Faster determinant for matrices over gf2e (M4RIE)
    
This computes the determinant for matrices over `GF(2**e)` by directly
relying on the PLE decomposition of M4RIE. This provides a significant
speed-up over the generic implementation used until now for this class.

Before:
```
sage: mat = matrix.random(GF(2**4), 200, 200)
sage: %time dd = mat.det()
CPU times: user 706 ms, sys: 2.26 ms, total: 708 ms
Wall time: 705 ms
sage: mat = matrix.random(GF(2**8), 200, 200)
sage: %time dd = mat.det()
CPU times: user 746 ms, sys: 890 µs, total: 747 ms
Wall time: 744 ms
sage: mat = matrix.random(GF(2**15), 200, 200)
sage: %time dd = mat.det()
CPU times: user 1.37 s, sys: 4.74 ms, total: 1.38 s
Wall time: 1.37 s
sage: mat = matrix.random(GF(2**4), 500, 500)
sage: %time dd = mat.det()
CPU times: user 10.8 s, sys: 23.8 ms, total: 10.8 s
Wall time: 10.7 s
sage: mat = matrix.random(GF(2**8), 500, 500)
sage: %time dd = mat.det()
CPU times: user 11.6 s, sys: 22.1 ms, total: 11.6 s
Wall time: 11.6 s
sage: mat = matrix.random(GF(2**15), 500, 500)
sage: %time dd = mat.det()
CPU times: user 21.7 s, sys: 37 ms, total: 21.7 s
Wall time: 21.7 s
```

After:
```
sage: mat = matrix.random(GF(2**4), 200, 200)
sage: %time dd = mat.det()
CPU times: user 275 µs, sys: 68 µs, total: 343 µs
Wall time: 356 µs
sage: mat = matrix.random(GF(2**8), 200, 200)
sage: %time dd = mat.det()
CPU times: user 1.1 ms, sys: 21 µs, total: 1.12 ms
Wall time: 1.13 ms
sage: mat = matrix.random(GF(2**15), 200, 200)
sage: %time dd = mat.det()
CPU times: user 81.2 ms, sys: 8.98 ms, total: 90.1 ms
Wall time: 89.7 ms
sage: mat = matrix.random(GF(2**4), 500, 500)
sage: %time dd = mat.det()
CPU times: user 1.84 ms, sys: 0 ns, total: 1.84 ms
Wall time: 1.86 ms
sage: mat = matrix.random(GF(2**8), 500, 500)
sage: %time dd = mat.det()
CPU times: user 5.23 ms, sys: 39 µs, total: 5.27 ms
Wall time: 5.44 ms
sage: mat = matrix.random(GF(2**15), 500, 500)
sage: %time dd = mat.det()
CPU times: user 719 ms, sys: 17.6 ms, total: 737 ms
Wall time: 732 ms
```
    
URL: sagemath#40773
Reported by: Vincent Neiger
Reviewer(s): user202729, Vincent Neiger
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants