_dfm.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  1. #
  2. # sympy.polys.matrices.dfm
  3. #
  4. # This modules defines the DFM class which is a wrapper for dense flint
  5. # matrices as found in python-flint.
  6. #
  7. # As of python-flint 0.4.1 matrices over the following domains can be supported
  8. # by python-flint:
  9. #
  10. # ZZ: flint.fmpz_mat
  11. # QQ: flint.fmpq_mat
  12. # GF(p): flint.nmod_mat (p prime and p < ~2**62)
  13. #
  14. # The underlying flint library has many more domains, but these are not yet
  15. # supported by python-flint.
  16. #
  17. # The DFM class is a wrapper for the flint matrices and provides a common
  18. # interface for all supported domains that is interchangeable with the DDM
  19. # and SDM classes so that DomainMatrix can be used with any as its internal
  20. # matrix representation.
  21. #
  22. # TODO:
  23. #
  24. # Implement the following methods that are provided by python-flint:
  25. #
  26. # - hnf (Hermite normal form)
  27. # - snf (Smith normal form)
  28. # - minpoly
  29. # - is_hnf
  30. # - is_snf
  31. # - rank
  32. #
  33. # The other types DDM and SDM do not have these methods and the algorithms
  34. # for hnf, snf and rank are already implemented. Algorithms for minpoly,
  35. # is_hnf and is_snf would need to be added.
  36. #
  37. # Add more methods to python-flint to expose more of Flint's functionality
  38. # and also to make some of the above methods simpler or more efficient e.g.
  39. # slicing, fancy indexing etc.
  40. from sympy.external.gmpy import GROUND_TYPES
  41. from sympy.external.importtools import import_module
  42. from sympy.utilities.decorator import doctest_depends_on
  43. from sympy.polys.domains import ZZ, QQ
  44. from .exceptions import (
  45. DMBadInputError,
  46. DMDomainError,
  47. DMNonSquareMatrixError,
  48. DMNonInvertibleMatrixError,
  49. DMRankError,
  50. DMShapeError,
  51. DMValueError,
  52. )
  53. if GROUND_TYPES != 'flint':
  54. __doctest_skip__ = ['*']
  55. flint = import_module('flint')
  56. __all__ = ['DFM']
  57. @doctest_depends_on(ground_types=['flint'])
  58. class DFM:
  59. """
  60. Dense FLINT matrix. This class is a wrapper for matrices from python-flint.
  61. >>> from sympy.polys.domains import ZZ
  62. >>> from sympy.polys.matrices.dfm import DFM
  63. >>> dfm = DFM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
  64. >>> dfm
  65. [[1, 2], [3, 4]]
  66. >>> dfm.rep
  67. [1, 2]
  68. [3, 4]
  69. >>> type(dfm.rep) # doctest: +SKIP
  70. <class 'flint._flint.fmpz_mat'>
  71. Usually, the DFM class is not instantiated directly, but is created as the
  72. internal representation of :class:`~.DomainMatrix`. When
  73. `SYMPY_GROUND_TYPES` is set to `flint` and `python-flint` is installed, the
  74. :class:`DFM` class is used automatically as the internal representation of
  75. :class:`~.DomainMatrix` in dense format if the domain is supported by
  76. python-flint.
  77. >>> from sympy.polys.matrices.domainmatrix import DM
  78. >>> dM = DM([[1, 2], [3, 4]], ZZ)
  79. >>> dM.rep
  80. [[1, 2], [3, 4]]
  81. A :class:`~.DomainMatrix` can be converted to :class:`DFM` by calling the
  82. :meth:`to_dfm` method:
  83. >>> dM.to_dfm()
  84. [[1, 2], [3, 4]]
  85. """
  86. fmt = 'dense'
  87. is_DFM = True
  88. is_DDM = False
  89. def __new__(cls, rowslist, shape, domain):
  90. """Construct from a nested list."""
  91. flint_mat = cls._get_flint_func(domain)
  92. if 0 not in shape:
  93. try:
  94. rep = flint_mat(rowslist)
  95. except (ValueError, TypeError):
  96. raise DMBadInputError(f"Input should be a list of list of {domain}")
  97. else:
  98. rep = flint_mat(*shape)
  99. return cls._new(rep, shape, domain)
  100. @classmethod
  101. def _new(cls, rep, shape, domain):
  102. """Internal constructor from a flint matrix."""
  103. cls._check(rep, shape, domain)
  104. obj = object.__new__(cls)
  105. obj.rep = rep
  106. obj.shape = obj.rows, obj.cols = shape
  107. obj.domain = domain
  108. return obj
  109. def _new_rep(self, rep):
  110. """Create a new DFM with the same shape and domain but a new rep."""
  111. return self._new(rep, self.shape, self.domain)
  112. @classmethod
  113. def _check(cls, rep, shape, domain):
  114. repshape = (rep.nrows(), rep.ncols())
  115. if repshape != shape:
  116. raise DMBadInputError("Shape of rep does not match shape of DFM")
  117. if domain == ZZ and not isinstance(rep, flint.fmpz_mat):
  118. raise RuntimeError("Rep is not a flint.fmpz_mat")
  119. elif domain == QQ and not isinstance(rep, flint.fmpq_mat):
  120. raise RuntimeError("Rep is not a flint.fmpq_mat")
  121. elif domain.is_FF and not isinstance(rep, (flint.fmpz_mod_mat, flint.nmod_mat)):
  122. raise RuntimeError("Rep is not a flint.fmpz_mod_mat or flint.nmod_mat")
  123. elif domain not in (ZZ, QQ) and not domain.is_FF:
  124. raise NotImplementedError("Only ZZ and QQ are supported by DFM")
  125. @classmethod
  126. def _supports_domain(cls, domain):
  127. """Return True if the given domain is supported by DFM."""
  128. return domain in (ZZ, QQ) or domain.is_FF and domain._is_flint
  129. @classmethod
  130. def _get_flint_func(cls, domain):
  131. """Return the flint matrix class for the given domain."""
  132. if domain == ZZ:
  133. return flint.fmpz_mat
  134. elif domain == QQ:
  135. return flint.fmpq_mat
  136. elif domain.is_FF:
  137. c = domain.characteristic()
  138. if isinstance(domain.one, flint.nmod):
  139. _cls = flint.nmod_mat
  140. def _func(*e):
  141. if len(e) == 1 and isinstance(e[0], flint.nmod_mat):
  142. return _cls(e[0])
  143. else:
  144. return _cls(*e, c)
  145. else:
  146. m = flint.fmpz_mod_ctx(c)
  147. _func = lambda *e: flint.fmpz_mod_mat(*e, m)
  148. return _func
  149. else:
  150. raise NotImplementedError("Only ZZ and QQ are supported by DFM")
  151. @property
  152. def _func(self):
  153. """Callable to create a flint matrix of the same domain."""
  154. return self._get_flint_func(self.domain)
  155. def __str__(self):
  156. """Return ``str(self)``."""
  157. return str(self.to_ddm())
  158. def __repr__(self):
  159. """Return ``repr(self)``."""
  160. return f'DFM{repr(self.to_ddm())[3:]}'
  161. def __eq__(self, other):
  162. """Return ``self == other``."""
  163. if not isinstance(other, DFM):
  164. return NotImplemented
  165. # Compare domains first because we do *not* want matrices with
  166. # different domains to be equal but e.g. a flint fmpz_mat and fmpq_mat
  167. # with the same entries will compare equal.
  168. return self.domain == other.domain and self.rep == other.rep
  169. @classmethod
  170. def from_list(cls, rowslist, shape, domain):
  171. """Construct from a nested list."""
  172. return cls(rowslist, shape, domain)
  173. def to_list(self):
  174. """Convert to a nested list."""
  175. return self.rep.tolist()
  176. def copy(self):
  177. """Return a copy of self."""
  178. return self._new_rep(self._func(self.rep))
  179. def to_ddm(self):
  180. """Convert to a DDM."""
  181. return DDM.from_list(self.to_list(), self.shape, self.domain)
  182. def to_sdm(self):
  183. """Convert to a SDM."""
  184. return SDM.from_list(self.to_list(), self.shape, self.domain)
  185. def to_dfm(self):
  186. """Return self."""
  187. return self
  188. def to_dfm_or_ddm(self):
  189. """
  190. Convert to a :class:`DFM`.
  191. This :class:`DFM` method exists to parallel the :class:`~.DDM` and
  192. :class:`~.SDM` methods. For :class:`DFM` it will always return self.
  193. See Also
  194. ========
  195. to_ddm
  196. to_sdm
  197. sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm
  198. """
  199. return self
  200. @classmethod
  201. def from_ddm(cls, ddm):
  202. """Convert from a DDM."""
  203. return cls.from_list(ddm.to_list(), ddm.shape, ddm.domain)
  204. @classmethod
  205. def from_list_flat(cls, elements, shape, domain):
  206. """Inverse of :meth:`to_list_flat`."""
  207. func = cls._get_flint_func(domain)
  208. try:
  209. rep = func(*shape, elements)
  210. except ValueError:
  211. raise DMBadInputError(f"Incorrect number of elements for shape {shape}")
  212. except TypeError:
  213. raise DMBadInputError(f"Input should be a list of {domain}")
  214. return cls(rep, shape, domain)
  215. def to_list_flat(self):
  216. """Convert to a flat list."""
  217. return self.rep.entries()
  218. def to_flat_nz(self):
  219. """Convert to a flat list of non-zeros."""
  220. return self.to_ddm().to_flat_nz()
  221. @classmethod
  222. def from_flat_nz(cls, elements, data, domain):
  223. """Inverse of :meth:`to_flat_nz`."""
  224. return DDM.from_flat_nz(elements, data, domain).to_dfm()
  225. def to_dod(self):
  226. """Convert to a DOD."""
  227. return self.to_ddm().to_dod()
  228. @classmethod
  229. def from_dod(cls, dod, shape, domain):
  230. """Inverse of :meth:`to_dod`."""
  231. return DDM.from_dod(dod, shape, domain).to_dfm()
  232. def to_dok(self):
  233. """Convert to a DOK."""
  234. return self.to_ddm().to_dok()
  235. @classmethod
  236. def from_dok(cls, dok, shape, domain):
  237. """Inverse of :math:`to_dod`."""
  238. return DDM.from_dok(dok, shape, domain).to_dfm()
  239. def iter_values(self):
  240. """Iterate over the non-zero values of the matrix."""
  241. m, n = self.shape
  242. rep = self.rep
  243. for i in range(m):
  244. for j in range(n):
  245. repij = rep[i, j]
  246. if repij:
  247. yield rep[i, j]
  248. def iter_items(self):
  249. """Iterate over indices and values of nonzero elements of the matrix."""
  250. m, n = self.shape
  251. rep = self.rep
  252. for i in range(m):
  253. for j in range(n):
  254. repij = rep[i, j]
  255. if repij:
  256. yield ((i, j), repij)
  257. def convert_to(self, domain):
  258. """Convert to a new domain."""
  259. if domain == self.domain:
  260. return self.copy()
  261. elif domain == QQ and self.domain == ZZ:
  262. return self._new(flint.fmpq_mat(self.rep), self.shape, domain)
  263. elif self._supports_domain(domain):
  264. # XXX: Use more efficient conversions when possible.
  265. return self.to_ddm().convert_to(domain).to_dfm()
  266. else:
  267. # It is the callers responsibility to convert to DDM before calling
  268. # this method if the domain is not supported by DFM.
  269. raise NotImplementedError("Only ZZ and QQ are supported by DFM")
  270. def getitem(self, i, j):
  271. """Get the ``(i, j)``-th entry."""
  272. # XXX: flint matrices do not support negative indices
  273. # XXX: They also raise ValueError instead of IndexError
  274. m, n = self.shape
  275. if i < 0:
  276. i += m
  277. if j < 0:
  278. j += n
  279. try:
  280. return self.rep[i, j]
  281. except ValueError:
  282. raise IndexError(f"Invalid indices ({i}, {j}) for Matrix of shape {self.shape}")
  283. def setitem(self, i, j, value):
  284. """Set the ``(i, j)``-th entry."""
  285. # XXX: flint matrices do not support negative indices
  286. # XXX: They also raise ValueError instead of IndexError
  287. m, n = self.shape
  288. if i < 0:
  289. i += m
  290. if j < 0:
  291. j += n
  292. try:
  293. self.rep[i, j] = value
  294. except ValueError:
  295. raise IndexError(f"Invalid indices ({i}, {j}) for Matrix of shape {self.shape}")
  296. def _extract(self, i_indices, j_indices):
  297. """Extract a submatrix with no checking."""
  298. # Indices must be positive and in range.
  299. M = self.rep
  300. lol = [[M[i, j] for j in j_indices] for i in i_indices]
  301. shape = (len(i_indices), len(j_indices))
  302. return self.from_list(lol, shape, self.domain)
  303. def extract(self, rowslist, colslist):
  304. """Extract a submatrix."""
  305. # XXX: flint matrices do not support fancy indexing or negative indices
  306. #
  307. # Check and convert negative indices before calling _extract.
  308. m, n = self.shape
  309. new_rows = []
  310. new_cols = []
  311. for i in rowslist:
  312. if i < 0:
  313. i_pos = i + m
  314. else:
  315. i_pos = i
  316. if not 0 <= i_pos < m:
  317. raise IndexError(f"Invalid row index {i} for Matrix of shape {self.shape}")
  318. new_rows.append(i_pos)
  319. for j in colslist:
  320. if j < 0:
  321. j_pos = j + n
  322. else:
  323. j_pos = j
  324. if not 0 <= j_pos < n:
  325. raise IndexError(f"Invalid column index {j} for Matrix of shape {self.shape}")
  326. new_cols.append(j_pos)
  327. return self._extract(new_rows, new_cols)
  328. def extract_slice(self, rowslice, colslice):
  329. """Slice a DFM."""
  330. # XXX: flint matrices do not support slicing
  331. m, n = self.shape
  332. i_indices = range(m)[rowslice]
  333. j_indices = range(n)[colslice]
  334. return self._extract(i_indices, j_indices)
  335. def neg(self):
  336. """Negate a DFM matrix."""
  337. return self._new_rep(-self.rep)
  338. def add(self, other):
  339. """Add two DFM matrices."""
  340. return self._new_rep(self.rep + other.rep)
  341. def sub(self, other):
  342. """Subtract two DFM matrices."""
  343. return self._new_rep(self.rep - other.rep)
  344. def mul(self, other):
  345. """Multiply a DFM matrix from the right by a scalar."""
  346. return self._new_rep(self.rep * other)
  347. def rmul(self, other):
  348. """Multiply a DFM matrix from the left by a scalar."""
  349. return self._new_rep(other * self.rep)
  350. def mul_elementwise(self, other):
  351. """Elementwise multiplication of two DFM matrices."""
  352. # XXX: flint matrices do not support elementwise multiplication
  353. return self.to_ddm().mul_elementwise(other.to_ddm()).to_dfm()
  354. def matmul(self, other):
  355. """Multiply two DFM matrices."""
  356. shape = (self.rows, other.cols)
  357. return self._new(self.rep * other.rep, shape, self.domain)
  358. # XXX: For the most part DomainMatrix does not expect DDM, SDM, or DFM to
  359. # have arithmetic operators defined. The only exception is negation.
  360. # Perhaps that should be removed.
  361. def __neg__(self):
  362. """Negate a DFM matrix."""
  363. return self.neg()
  364. @classmethod
  365. def zeros(cls, shape, domain):
  366. """Return a zero DFM matrix."""
  367. func = cls._get_flint_func(domain)
  368. return cls._new(func(*shape), shape, domain)
  369. # XXX: flint matrices do not have anything like ones or eye
  370. # In the methods below we convert to DDM and then back to DFM which is
  371. # probably about as efficient as implementing these methods directly.
  372. @classmethod
  373. def ones(cls, shape, domain):
  374. """Return a one DFM matrix."""
  375. # XXX: flint matrices do not have anything like ones
  376. return DDM.ones(shape, domain).to_dfm()
  377. @classmethod
  378. def eye(cls, n, domain):
  379. """Return the identity matrix of size n."""
  380. # XXX: flint matrices do not have anything like eye
  381. return DDM.eye(n, domain).to_dfm()
  382. @classmethod
  383. def diag(cls, elements, domain):
  384. """Return a diagonal matrix."""
  385. return DDM.diag(elements, domain).to_dfm()
  386. def applyfunc(self, func, domain):
  387. """Apply a function to each entry of a DFM matrix."""
  388. return self.to_ddm().applyfunc(func, domain).to_dfm()
  389. def transpose(self):
  390. """Transpose a DFM matrix."""
  391. return self._new(self.rep.transpose(), (self.cols, self.rows), self.domain)
  392. def hstack(self, *others):
  393. """Horizontally stack matrices."""
  394. return self.to_ddm().hstack(*[o.to_ddm() for o in others]).to_dfm()
  395. def vstack(self, *others):
  396. """Vertically stack matrices."""
  397. return self.to_ddm().vstack(*[o.to_ddm() for o in others]).to_dfm()
  398. def diagonal(self):
  399. """Return the diagonal of a DFM matrix."""
  400. M = self.rep
  401. m, n = self.shape
  402. return [M[i, i] for i in range(min(m, n))]
  403. def is_upper(self):
  404. """Return ``True`` if the matrix is upper triangular."""
  405. M = self.rep
  406. for i in range(self.rows):
  407. for j in range(min(i, self.cols)):
  408. if M[i, j]:
  409. return False
  410. return True
  411. def is_lower(self):
  412. """Return ``True`` if the matrix is lower triangular."""
  413. M = self.rep
  414. for i in range(self.rows):
  415. for j in range(i + 1, self.cols):
  416. if M[i, j]:
  417. return False
  418. return True
  419. def is_diagonal(self):
  420. """Return ``True`` if the matrix is diagonal."""
  421. return self.is_upper() and self.is_lower()
  422. def is_zero_matrix(self):
  423. """Return ``True`` if the matrix is the zero matrix."""
  424. M = self.rep
  425. for i in range(self.rows):
  426. for j in range(self.cols):
  427. if M[i, j]:
  428. return False
  429. return True
  430. def nnz(self):
  431. """Return the number of non-zero elements in the matrix."""
  432. return self.to_ddm().nnz()
  433. def scc(self):
  434. """Return the strongly connected components of the matrix."""
  435. return self.to_ddm().scc()
  436. @doctest_depends_on(ground_types='flint')
  437. def det(self):
  438. """
  439. Compute the determinant of the matrix using FLINT.
  440. Examples
  441. ========
  442. >>> from sympy import Matrix
  443. >>> M = Matrix([[1, 2], [3, 4]])
  444. >>> dfm = M.to_DM().to_dfm()
  445. >>> dfm
  446. [[1, 2], [3, 4]]
  447. >>> dfm.det()
  448. -2
  449. Notes
  450. =====
  451. Calls the ``.det()`` method of the underlying FLINT matrix.
  452. For :ref:`ZZ` or :ref:`QQ` this calls ``fmpz_mat_det`` or
  453. ``fmpq_mat_det`` respectively.
  454. At the time of writing the implementation of ``fmpz_mat_det`` uses one
  455. of several algorithms depending on the size of the matrix and bit size
  456. of the entries. The algorithms used are:
  457. - Cofactor for very small (up to 4x4) matrices.
  458. - Bareiss for small (up to 25x25) matrices.
  459. - Modular algorithms for larger matrices (up to 60x60) or for larger
  460. matrices with large bit sizes.
  461. - Modular "accelerated" for larger matrices (60x60 upwards) if the bit
  462. size is smaller than the dimensions of the matrix.
  463. The implementation of ``fmpq_mat_det`` clears denominators from each
  464. row (not the whole matrix) and then calls ``fmpz_mat_det`` and divides
  465. by the product of the denominators.
  466. See Also
  467. ========
  468. sympy.polys.matrices.domainmatrix.DomainMatrix.det
  469. Higher level interface to compute the determinant of a matrix.
  470. """
  471. # XXX: At least the first three algorithms described above should also
  472. # be implemented in the pure Python DDM and SDM classes which at the
  473. # time of writng just use Bareiss for all matrices and domains.
  474. # Probably in Python the thresholds would be different though.
  475. return self.rep.det()
  476. @doctest_depends_on(ground_types='flint')
  477. def charpoly(self):
  478. """
  479. Compute the characteristic polynomial of the matrix using FLINT.
  480. Examples
  481. ========
  482. >>> from sympy import Matrix
  483. >>> M = Matrix([[1, 2], [3, 4]])
  484. >>> dfm = M.to_DM().to_dfm() # need ground types = 'flint'
  485. >>> dfm
  486. [[1, 2], [3, 4]]
  487. >>> dfm.charpoly()
  488. [1, -5, -2]
  489. Notes
  490. =====
  491. Calls the ``.charpoly()`` method of the underlying FLINT matrix.
  492. For :ref:`ZZ` or :ref:`QQ` this calls ``fmpz_mat_charpoly`` or
  493. ``fmpq_mat_charpoly`` respectively.
  494. At the time of writing the implementation of ``fmpq_mat_charpoly``
  495. clears a denominator from the whole matrix and then calls
  496. ``fmpz_mat_charpoly``. The coefficients of the characteristic
  497. polynomial are then multiplied by powers of the denominator.
  498. The ``fmpz_mat_charpoly`` method uses a modular algorithm with CRT
  499. reconstruction. The modular algorithm uses ``nmod_mat_charpoly`` which
  500. uses Berkowitz for small matrices and non-prime moduli or otherwise
  501. the Danilevsky method.
  502. See Also
  503. ========
  504. sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly
  505. Higher level interface to compute the characteristic polynomial of
  506. a matrix.
  507. """
  508. # FLINT polynomial coefficients are in reverse order compared to SymPy.
  509. return self.rep.charpoly().coeffs()[::-1]
  510. @doctest_depends_on(ground_types='flint')
  511. def inv(self):
  512. """
  513. Compute the inverse of a matrix using FLINT.
  514. Examples
  515. ========
  516. >>> from sympy import Matrix, QQ
  517. >>> M = Matrix([[1, 2], [3, 4]])
  518. >>> dfm = M.to_DM().to_dfm().convert_to(QQ)
  519. >>> dfm
  520. [[1, 2], [3, 4]]
  521. >>> dfm.inv()
  522. [[-2, 1], [3/2, -1/2]]
  523. >>> dfm.matmul(dfm.inv())
  524. [[1, 0], [0, 1]]
  525. Notes
  526. =====
  527. Calls the ``.inv()`` method of the underlying FLINT matrix.
  528. For now this will raise an error if the domain is :ref:`ZZ` but will
  529. use the FLINT method for :ref:`QQ`.
  530. The FLINT methods for :ref:`ZZ` and :ref:`QQ` are ``fmpz_mat_inv`` and
  531. ``fmpq_mat_inv`` respectively. The ``fmpz_mat_inv`` method computes an
  532. inverse with denominator. This is implemented by calling
  533. ``fmpz_mat_solve`` (see notes in :meth:`lu_solve` about the algorithm).
  534. The ``fmpq_mat_inv`` method clears denominators from each row and then
  535. multiplies those into the rhs identity matrix before calling
  536. ``fmpz_mat_solve``.
  537. See Also
  538. ========
  539. sympy.polys.matrices.domainmatrix.DomainMatrix.inv
  540. Higher level method for computing the inverse of a matrix.
  541. """
  542. # TODO: Implement similar algorithms for DDM and SDM.
  543. #
  544. # XXX: The flint fmpz_mat and fmpq_mat inv methods both return fmpq_mat
  545. # by default. The fmpz_mat method has an optional argument to return
  546. # fmpz_mat instead for unimodular matrices.
  547. #
  548. # The convention in DomainMatrix is to raise an error if the matrix is
  549. # not over a field regardless of whether the matrix is invertible over
  550. # its domain or over any associated field. Maybe DomainMatrix.inv
  551. # should be changed to always return a matrix over an associated field
  552. # except with a unimodular argument for returning an inverse over a
  553. # ring if possible.
  554. #
  555. # For now we follow the existing DomainMatrix convention...
  556. K = self.domain
  557. m, n = self.shape
  558. if m != n:
  559. raise DMNonSquareMatrixError("cannot invert a non-square matrix")
  560. if K == ZZ:
  561. raise DMDomainError("field expected, got %s" % K)
  562. elif K == QQ or K.is_FF:
  563. try:
  564. return self._new_rep(self.rep.inv())
  565. except ZeroDivisionError:
  566. raise DMNonInvertibleMatrixError("matrix is not invertible")
  567. else:
  568. # If more domains are added for DFM then we will need to consider
  569. # what happens here.
  570. raise NotImplementedError("DFM.inv() is not implemented for %s" % K)
  571. def lu(self):
  572. """Return the LU decomposition of the matrix."""
  573. L, U, swaps = self.to_ddm().lu()
  574. return L.to_dfm(), U.to_dfm(), swaps
  575. def qr(self):
  576. """Return the QR decomposition of the matrix."""
  577. Q, R = self.to_ddm().qr()
  578. return Q.to_dfm(), R.to_dfm()
  579. # XXX: The lu_solve function should be renamed to solve. Whether or not it
  580. # uses an LU decomposition is an implementation detail. A method called
  581. # lu_solve would make sense for a situation in which an LU decomposition is
  582. # reused several times to solve with different rhs but that would imply a
  583. # different call signature.
  584. #
  585. # The underlying python-flint method has an algorithm= argument so we could
  586. # use that and have e.g. solve_lu and solve_modular or perhaps also a
  587. # method= argument to choose between the two. Flint itself has more
  588. # possible algorithms to choose from than are exposed by python-flint.
  589. @doctest_depends_on(ground_types='flint')
  590. def lu_solve(self, rhs):
  591. """
  592. Solve a matrix equation using FLINT.
  593. Examples
  594. ========
  595. >>> from sympy import Matrix, QQ
  596. >>> M = Matrix([[1, 2], [3, 4]])
  597. >>> dfm = M.to_DM().to_dfm().convert_to(QQ)
  598. >>> dfm
  599. [[1, 2], [3, 4]]
  600. >>> rhs = Matrix([1, 2]).to_DM().to_dfm().convert_to(QQ)
  601. >>> dfm.lu_solve(rhs)
  602. [[0], [1/2]]
  603. Notes
  604. =====
  605. Calls the ``.solve()`` method of the underlying FLINT matrix.
  606. For now this will raise an error if the domain is :ref:`ZZ` but will
  607. use the FLINT method for :ref:`QQ`.
  608. The FLINT methods for :ref:`ZZ` and :ref:`QQ` are ``fmpz_mat_solve``
  609. and ``fmpq_mat_solve`` respectively. The ``fmpq_mat_solve`` method
  610. uses one of two algorithms:
  611. - For small matrices (<25 rows) it clears denominators between the
  612. matrix and rhs and uses ``fmpz_mat_solve``.
  613. - For larger matrices it uses ``fmpq_mat_solve_dixon`` which is a
  614. modular approach with CRT reconstruction over :ref:`QQ`.
  615. The ``fmpz_mat_solve`` method uses one of four algorithms:
  616. - For very small (<= 3x3) matrices it uses a Cramer's rule.
  617. - For small (<= 15x15) matrices it uses a fraction-free LU solve.
  618. - Otherwise it uses either Dixon or another multimodular approach.
  619. See Also
  620. ========
  621. sympy.polys.matrices.domainmatrix.DomainMatrix.lu_solve
  622. Higher level interface to solve a matrix equation.
  623. """
  624. if not self.domain == rhs.domain:
  625. raise DMDomainError("Domains must match: %s != %s" % (self.domain, rhs.domain))
  626. # XXX: As for inv we should consider whether to return a matrix over
  627. # over an associated field or attempt to find a solution in the ring.
  628. # For now we follow the existing DomainMatrix convention...
  629. if not self.domain.is_Field:
  630. raise DMDomainError("Field expected, got %s" % self.domain)
  631. m, n = self.shape
  632. j, k = rhs.shape
  633. if m != j:
  634. raise DMShapeError("Matrix size mismatch: %s * %s vs %s * %s" % (m, n, j, k))
  635. sol_shape = (n, k)
  636. # XXX: The Flint solve method only handles square matrices. Probably
  637. # Flint has functions that could be used to solve non-square systems
  638. # but they are not exposed in python-flint yet. Alternatively we could
  639. # put something here using the features that are available like rref.
  640. if m != n:
  641. return self.to_ddm().lu_solve(rhs.to_ddm()).to_dfm()
  642. try:
  643. sol = self.rep.solve(rhs.rep)
  644. except ZeroDivisionError:
  645. raise DMNonInvertibleMatrixError("Matrix det == 0; not invertible.")
  646. return self._new(sol, sol_shape, self.domain)
  647. def fflu(self):
  648. """
  649. Fraction-free LU decomposition of DFM.
  650. Explanation
  651. ===========
  652. Uses `python-flint` if possible for a matrix of
  653. integers otherwise uses the DDM method.
  654. See Also
  655. ========
  656. sympy.polys.matrices.ddm.DDM.fflu
  657. """
  658. if self.domain == ZZ:
  659. fflu = getattr(self.rep, 'fflu', None)
  660. if fflu is not None:
  661. P, L, D, U = self.rep.fflu()
  662. m, n = self.shape
  663. return (
  664. self._new(P, (m, m), self.domain),
  665. self._new(L, (m, m), self.domain),
  666. self._new(D, (m, m), self.domain),
  667. self._new(U, self.shape, self.domain)
  668. )
  669. ddm_p, ddm_l, ddm_d, ddm_u = self.to_ddm().fflu()
  670. P = ddm_p.to_dfm()
  671. L = ddm_l.to_dfm()
  672. D = ddm_d.to_dfm()
  673. U = ddm_u.to_dfm()
  674. return P, L, D, U
  675. def nullspace(self):
  676. """Return a basis for the nullspace of the matrix."""
  677. # Code to compute nullspace using flint:
  678. #
  679. # V, nullity = self.rep.nullspace()
  680. # V_dfm = self._new_rep(V)._extract(range(self.rows), range(nullity))
  681. #
  682. # XXX: That gives the nullspace but does not give us nonpivots. So we
  683. # use the slower DDM method anyway. It would be better to change the
  684. # signature of the nullspace method to not return nonpivots.
  685. #
  686. # XXX: Also python-flint exposes a nullspace method for fmpz_mat but
  687. # not for fmpq_mat. This is the reverse of the situation for DDM etc
  688. # which only allow nullspace over a field. The nullspace method for
  689. # DDM, SDM etc should be changed to allow nullspace over ZZ as well.
  690. # The DomainMatrix nullspace method does allow the domain to be a ring
  691. # but does not directly call the lower-level nullspace methods and uses
  692. # rref_den instead. Nullspace methods should also be added to all
  693. # matrix types in python-flint.
  694. ddm, nonpivots = self.to_ddm().nullspace()
  695. return ddm.to_dfm(), nonpivots
  696. def nullspace_from_rref(self, pivots=None):
  697. """Return a basis for the nullspace of the matrix."""
  698. # XXX: Use the flint nullspace method!!!
  699. sdm, nonpivots = self.to_sdm().nullspace_from_rref(pivots=pivots)
  700. return sdm.to_dfm(), nonpivots
  701. def particular(self):
  702. """Return a particular solution to the system."""
  703. return self.to_ddm().particular().to_dfm()
  704. def _lll(self, transform=False, delta=0.99, eta=0.51, rep='zbasis', gram='approx'):
  705. """Call the fmpz_mat.lll() method but check rank to avoid segfaults."""
  706. # XXX: There are tests that pass e.g. QQ(5,6) for delta. That fails
  707. # with a TypeError in flint because if QQ is fmpq then conversion with
  708. # float fails. We handle that here but there are two better fixes:
  709. #
  710. # - Make python-flint's fmpq convert with float(x)
  711. # - Change the tests because delta should just be a float.
  712. def to_float(x):
  713. if QQ.of_type(x):
  714. return float(x.numerator) / float(x.denominator)
  715. else:
  716. return float(x)
  717. delta = to_float(delta)
  718. eta = to_float(eta)
  719. if not 0.25 < delta < 1:
  720. raise DMValueError("delta must be between 0.25 and 1")
  721. # XXX: The flint lll method segfaults if the matrix is not full rank.
  722. m, n = self.shape
  723. if self.rep.rank() != m:
  724. raise DMRankError("Matrix must have full row rank for Flint LLL.")
  725. # Actually call the flint method.
  726. return self.rep.lll(transform=transform, delta=delta, eta=eta, rep=rep, gram=gram)
  727. @doctest_depends_on(ground_types='flint')
  728. def lll(self, delta=0.75):
  729. """Compute LLL-reduced basis using FLINT.
  730. See :meth:`lll_transform` for more information.
  731. Examples
  732. ========
  733. >>> from sympy import Matrix
  734. >>> M = Matrix([[1, 2, 3], [4, 5, 6]])
  735. >>> M.to_DM().to_dfm().lll()
  736. [[2, 1, 0], [-1, 1, 3]]
  737. See Also
  738. ========
  739. sympy.polys.matrices.domainmatrix.DomainMatrix.lll
  740. Higher level interface to compute LLL-reduced basis.
  741. lll_transform
  742. Compute LLL-reduced basis and transform matrix.
  743. """
  744. if self.domain != ZZ:
  745. raise DMDomainError("ZZ expected, got %s" % self.domain)
  746. elif self.rows > self.cols:
  747. raise DMShapeError("Matrix must not have more rows than columns.")
  748. rep = self._lll(delta=delta)
  749. return self._new_rep(rep)
  750. @doctest_depends_on(ground_types='flint')
  751. def lll_transform(self, delta=0.75):
  752. """Compute LLL-reduced basis and transform using FLINT.
  753. Examples
  754. ========
  755. >>> from sympy import Matrix
  756. >>> M = Matrix([[1, 2, 3], [4, 5, 6]]).to_DM().to_dfm()
  757. >>> M_lll, T = M.lll_transform()
  758. >>> M_lll
  759. [[2, 1, 0], [-1, 1, 3]]
  760. >>> T
  761. [[-2, 1], [3, -1]]
  762. >>> T.matmul(M) == M_lll
  763. True
  764. See Also
  765. ========
  766. sympy.polys.matrices.domainmatrix.DomainMatrix.lll
  767. Higher level interface to compute LLL-reduced basis.
  768. lll
  769. Compute LLL-reduced basis without transform matrix.
  770. """
  771. if self.domain != ZZ:
  772. raise DMDomainError("ZZ expected, got %s" % self.domain)
  773. elif self.rows > self.cols:
  774. raise DMShapeError("Matrix must not have more rows than columns.")
  775. rep, T = self._lll(transform=True, delta=delta)
  776. basis = self._new_rep(rep)
  777. T_dfm = self._new(T, (self.rows, self.rows), self.domain)
  778. return basis, T_dfm
  779. # Avoid circular imports
  780. from sympy.polys.matrices.ddm import DDM
  781. from sympy.polys.matrices.ddm import SDM