Skip to content

Commit 3531a87

Browse files
author
Release Manager
committed
gh-39733: Make rational matrix rref default to flint_multimodular, add suboptions for flint algorithm Because one of the algorithms used by flint is multimodular, it ought to be faster than the implementation in Python. At least after we upgrade to a version after flintlib/flint#2129 . (p/s: if someone uses the old version, the current choice of flint might be slower in some cases, see the linked issue. An alternative which is likely always faster is to explicitly use the multimodular algorithm in flint. Do you think the current implementation is fine, or should we provide an explicit `flint_multimodular` option instead?) Fixes #39197 ### 📝 Checklist <!-- Put an `x` in all the boxes that apply. --> - [x] The title is concise and informative. - [x] The description explains in detail what this PR is about. - [x] I have linked a relevant issue or discussion. - [ ] I have created tests covering the changes. - [ ] I have updated the documentation and checked the documentation preview. ### ⌛ Dependencies <!-- List all open PRs that this PR logically depends on. For example, --> <!-- - #12345: short description why this is a dependency --> <!-- - #34567: ... --> #39204 URL: #39733 Reported by: user202729 Reviewer(s): Travis Scrimshaw
2 parents 21b8845 + 5fa99ec commit 3531a87

File tree

2 files changed

+71
-25
lines changed

2 files changed

+71
-25
lines changed

src/sage/matrix/matrix_rational_dense.pyx

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,24 +1499,33 @@ cdef class Matrix_rational_dense(Matrix_dense):
14991499
15001500
- ``algorithm`` -- an optional specification of an algorithm. One of
15011501
1502-
- ``None``: (default) uses flint for small dimension and multimodular otherwise
1502+
- ``None``: (default) try to pick the best choice,
15031503
1504-
- ``'flint'``: use the flint library,
1504+
- ``'flint'``: use flint library
1505+
`function <https://flintlib.org/doc/fmpq_mat.html#c.fmpq_mat_rref>`_,
1506+
which automatically chooses between
1507+
`classical algorithm <https://flintlib.org/doc/fmpq_mat.html#c.fmpq_mat_rref_classical>`_
1508+
(Gaussian elimination),
1509+
`fraction-free multimodular <https://flintlib.org/doc/fmpz_mat.html#c.fmpz_mat_rref_mul>`_,
1510+
and `fraction-free LU decomposition <https://flintlib.org/doc/fmpz_mat.html#c.fmpz_mat_rref_fflu>`_,
1511+
1512+
- ``'flint:classical'``, ``'flint:multimodular'``, ``'flint:fflu'``: use the
1513+
flint library as above, but select an algorithm explicitly,
15051514
15061515
- ``'padic'``: an algorithm based on the IML `p`-adic solver,
15071516
1508-
- ``'multimodular'``: uses a multimodular algorithm the uses
1509-
linbox modulo many primes (likely to be faster when coefficients
1510-
are huge),
1517+
- ``'multimodular'``: uses a multimodular algorithm implemented in Cython
1518+
that uses linbox modulo many primes,
1519+
see :func:`~sage.matrix.misc.matrix_rational_echelon_form_multimodular`,
15111520
15121521
- ``'classical'``: just clear each column using Gauss elimination.
15131522
15141523
- ``height_guess``, ``**kwds`` -- all passed to the
1515-
multimodular algorithm; ignored by other algorithms
1524+
``'multimodular'`` algorithm; ignored by other algorithms
15161525
15171526
- ``proof`` -- boolean or ``None`` (default: None, see
15181527
proof.linear_algebra or sage.structure.proof). Passed to the
1519-
multimodular algorithm. Note that the Sage global default is
1528+
``'multimodular'`` algorithm. Note that the Sage global default is
15201529
``proof=True``.
15211530
15221531
EXAMPLES::
@@ -1548,7 +1557,8 @@ cdef class Matrix_rational_dense(Matrix_dense):
15481557
Echelonizing a matrix in place throws away the cache of
15491558
the old matrix (:issue:`14506`)::
15501559
1551-
sage: for algo in ["flint", "padic", "multimodular", "classical"]:
1560+
sage: for algo in ["flint", "padic", "multimodular", "classical", "flint:classical",
1561+
....: "flint:multimodular", "flint:fflu"]:
15521562
....: a = Matrix(QQ, [[1,2],[3,4]])
15531563
....: _ = a.det() # fills the cache
15541564
....: _ = a._clear_denom() # fills the cache
@@ -1560,13 +1570,10 @@ cdef class Matrix_rational_dense(Matrix_dense):
15601570
self.check_mutability()
15611571

15621572
if algorithm is None:
1563-
if self._nrows <= 25 or self._ncols <= 25:
1564-
algorithm = 'flint'
1565-
else:
1566-
algorithm = 'multimodular'
1573+
algorithm = 'flint:multimodular'
15671574

1568-
if algorithm == 'flint':
1569-
pivots = self._echelonize_flint()
1575+
if algorithm in ('flint', 'flint:classical', 'flint:multimodular', 'flint:fflu'):
1576+
pivots = self._echelonize_flint(algorithm)
15701577
elif algorithm == 'multimodular':
15711578
pivots = self._echelonize_multimodular(height_guess, proof, **kwds)
15721579
elif algorithm == 'classical':
@@ -1656,20 +1663,24 @@ cdef class Matrix_rational_dense(Matrix_dense):
16561663
self.cache('rank', len(E.pivots()))
16571664
return E
16581665

1659-
def _echelonize_flint(self):
1666+
def _echelonize_flint(self, algorithm: str):
16601667
r"""
1668+
INPUT: See :meth:`echelonize` for the options.
1669+
Only options that use flint are allowed, passing other algorithms may
1670+
trigger undefined behavior.
1671+
16611672
EXAMPLES::
16621673
16631674
sage: m = matrix(QQ, 4, range(16))
1664-
sage: m._echelonize_flint()
1675+
sage: m._echelonize_flint("flint")
16651676
(0, 1)
16661677
sage: m
16671678
[ 1 0 -1 -2]
16681679
[ 0 1 2 3]
16691680
[ 0 0 0 0]
16701681
[ 0 0 0 0]
16711682
sage: m = matrix(QQ, 4, 6, [-1,0,0,-2,-1,-2,-1,0,0,-2,-1,0,3,3,-2,0,0,3,-2,-3,1,1,-2,3])
1672-
sage: m._echelonize_flint()
1683+
sage: m._echelonize_flint("flint")
16731684
(0, 1, 2, 5)
16741685
sage: m
16751686
[ 1 0 0 2 1 0]
@@ -1678,10 +1689,40 @@ cdef class Matrix_rational_dense(Matrix_dense):
16781689
[ 0 0 0 0 0 1]
16791690
"""
16801691
self.clear_cache()
1692+
1693+
if fmpq_mat_is_empty(self._matrix):
1694+
return ()
1695+
16811696
cdef long r
1697+
cdef fmpz_mat_t Aclear
1698+
cdef fmpz_t den
16821699

16831700
sig_on()
1684-
r = fmpq_mat_rref(self._matrix, self._matrix)
1701+
1702+
if algorithm == 'flint':
1703+
r = fmpq_mat_rref(self._matrix, self._matrix)
1704+
elif algorithm == 'flint:classical':
1705+
r = fmpq_mat_rref_classical(self._matrix, self._matrix)
1706+
else:
1707+
# copied from fmpq_mat_rref_fraction_free
1708+
fmpz_mat_init(Aclear, self._nrows, self._ncols)
1709+
fmpq_mat_get_fmpz_mat_rowwise(Aclear, NULL, self._matrix)
1710+
fmpz_init(den)
1711+
1712+
if algorithm == 'flint:fflu':
1713+
r = fmpz_mat_rref_fflu(Aclear, den, Aclear)
1714+
else:
1715+
assert algorithm == 'flint:multimodular'
1716+
r = fmpz_mat_rref_mul(Aclear, den, Aclear)
1717+
1718+
if r == 0:
1719+
fmpq_mat_zero(self._matrix)
1720+
else:
1721+
fmpq_mat_set_fmpz_mat_div_fmpz(self._matrix, Aclear, den)
1722+
1723+
fmpz_mat_clear(Aclear)
1724+
fmpz_clear(den)
1725+
16851726
sig_off()
16861727

16871728
# compute pivots

src/sage/matrix/misc.pyx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,16 +205,16 @@ def matrix_rational_echelon_form_multimodular(Matrix self, height_guess=None, pr
205205
sage: A.pivots()
206206
(0, 1, 2)
207207
208-
A small benchmark, showing that the multimodular algorithm is sometimes faster
209-
and sometimes slower than the flint algorithm::
208+
A small benchmark, showing that flint fraction-free multimodular algorithm
209+
is always faster than the fraction-free multimodular algorithm implemented in Python::
210210
211211
sage: import copy
212212
sage: def benchmark(num_row, num_col, entry_size, timeout=2, integer_coefficient=True):
213213
....: A = matrix(QQ, [[
214214
....: randint(1, 2^entry_size) if integer_coefficient else ZZ(randint(1, 2^entry_size))/randint(1, 2^entry_size)
215215
....: for col in range(num_col)] for row in range(num_row)])
216216
....: data=[]
217-
....: for algorithm in ("flint", "padic", "multimodular"):
217+
....: for algorithm in ("flint:fflu", "flint:multimodular", "padic", "multimodular"):
218218
....: # classical is too slow
219219
....: B = copy.copy(A)
220220
....: t = walltime()
@@ -227,10 +227,15 @@ def matrix_rational_echelon_form_multimodular(Matrix self, height_guess=None, pr
227227
....: cancel_alarm()
228228
....: data.append((round(walltime(t), 4), algorithm))
229229
....: return sorted(data)
230-
sage: benchmark(20, 20, 10000) # long time (multimodular wins)
231-
[...'multimodular'...'flint'...]
232-
sage: benchmark(39, 40, 200) # long time (flint wins)
233-
[...'flint'...'multimodular'...]
230+
sage: benchmark(20, 20, 10000) # long time
231+
[...'flint:multimodular'...'multimodular'...'flint:fflu'...]
232+
sage: benchmark(39, 40, 200) # long time
233+
[...'flint:multimodular'...'flint:fflu'...'multimodular'...]
234+
235+
In older versions of flint
236+
before this `issue <https://github.com/flintlib/flint/issues/2129>`_
237+
is fixed, ``algorithm='flint'`` (automatic choice) may be slower than
238+
``algorithm='flint:multimodular'``.
234239
235240
In this case, there are more columns than rows, which means the resulting
236241
matrix has height much higher than the input matrix. We check that the function

0 commit comments

Comments
 (0)