ddm.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176
  1. """
  2. Module for the DDM class.
  3. The DDM class is an internal representation used by DomainMatrix. The letters
  4. DDM stand for Dense Domain Matrix. A DDM instance represents a matrix using
  5. elements from a polynomial Domain (e.g. ZZ, QQ, ...) in a dense-matrix
  6. representation.
  7. Basic usage:
  8. >>> from sympy import ZZ, QQ
  9. >>> from sympy.polys.matrices.ddm import DDM
  10. >>> A = DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
  11. >>> A.shape
  12. (2, 2)
  13. >>> A
  14. [[0, 1], [-1, 0]]
  15. >>> type(A)
  16. <class 'sympy.polys.matrices.ddm.DDM'>
  17. >>> A @ A
  18. [[-1, 0], [0, -1]]
  19. The ddm_* functions are designed to operate on DDM as well as on an ordinary
  20. list of lists:
  21. >>> from sympy.polys.matrices.dense import ddm_idet
  22. >>> ddm_idet(A, QQ)
  23. 1
  24. >>> ddm_idet([[0, 1], [-1, 0]], QQ)
  25. 1
  26. >>> A
  27. [[-1, 0], [0, -1]]
  28. Note that ddm_idet modifies the input matrix in-place. It is recommended to
  29. use the DDM.det method as a friendlier interface to this instead which takes
  30. care of copying the matrix:
  31. >>> B = DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
  32. >>> B.det()
  33. 1
  34. Normally DDM would not be used directly and is just part of the internal
  35. representation of DomainMatrix which adds further functionality including e.g.
  36. unifying domains.
  37. The dense format used by DDM is a list of lists of elements e.g. the 2x2
  38. identity matrix is like [[1, 0], [0, 1]]. The DDM class itself is a subclass
  39. of list and its list items are plain lists. Elements are accessed as e.g.
  40. ddm[i][j] where ddm[i] gives the ith row and ddm[i][j] gets the element in the
  41. jth column of that row. Subclassing list makes e.g. iteration and indexing
  42. very efficient. We do not override __getitem__ because it would lose that
  43. benefit.
  44. The core routines are implemented by the ddm_* functions defined in dense.py.
  45. Those functions are intended to be able to operate on a raw list-of-lists
  46. representation of matrices with most functions operating in-place. The DDM
  47. class takes care of copying etc and also stores a Domain object associated
  48. with its elements. This makes it possible to implement things like A + B with
  49. domain checking and also shape checking so that the list of lists
  50. representation is friendlier.
  51. """
  52. from itertools import chain
  53. from sympy.external.gmpy import GROUND_TYPES
  54. from sympy.utilities.decorator import doctest_depends_on
  55. from .exceptions import (
  56. DMBadInputError,
  57. DMDomainError,
  58. DMNonSquareMatrixError,
  59. DMShapeError,
  60. )
  61. from sympy.polys.domains import QQ
  62. from .dense import (
  63. ddm_transpose,
  64. ddm_iadd,
  65. ddm_isub,
  66. ddm_ineg,
  67. ddm_imul,
  68. ddm_irmul,
  69. ddm_imatmul,
  70. ddm_irref,
  71. ddm_irref_den,
  72. ddm_idet,
  73. ddm_iinv,
  74. ddm_ilu_split,
  75. ddm_ilu_solve,
  76. ddm_berk,
  77. )
  78. from .lll import ddm_lll, ddm_lll_transform
  79. if GROUND_TYPES != 'flint':
  80. __doctest_skip__ = ['DDM.to_dfm', 'DDM.to_dfm_or_ddm']
  81. class DDM(list):
  82. """Dense matrix based on polys domain elements
  83. This is a list subclass and is a wrapper for a list of lists that supports
  84. basic matrix arithmetic +, -, *, **.
  85. """
  86. fmt = 'dense'
  87. is_DFM = False
  88. is_DDM = True
  89. def __init__(self, rowslist, shape, domain):
  90. if not (isinstance(rowslist, list) and all(type(row) is list for row in rowslist)):
  91. raise DMBadInputError("rowslist must be a list of lists")
  92. m, n = shape
  93. if len(rowslist) != m or any(len(row) != n for row in rowslist):
  94. raise DMBadInputError("Inconsistent row-list/shape")
  95. super().__init__([i.copy() for i in rowslist])
  96. self.shape = (m, n)
  97. self.rows = m
  98. self.cols = n
  99. self.domain = domain
  100. def getitem(self, i, j):
  101. return self[i][j]
  102. def setitem(self, i, j, value):
  103. self[i][j] = value
  104. def extract_slice(self, slice1, slice2):
  105. ddm = [row[slice2] for row in self[slice1]]
  106. rows = len(ddm)
  107. cols = len(ddm[0]) if ddm else len(range(self.shape[1])[slice2])
  108. return DDM(ddm, (rows, cols), self.domain)
  109. def extract(self, rows, cols):
  110. ddm = []
  111. for i in rows:
  112. rowi = self[i]
  113. ddm.append([rowi[j] for j in cols])
  114. return DDM(ddm, (len(rows), len(cols)), self.domain)
  115. @classmethod
  116. def from_list(cls, rowslist, shape, domain):
  117. """
  118. Create a :class:`DDM` from a list of lists.
  119. Examples
  120. ========
  121. >>> from sympy import ZZ
  122. >>> from sympy.polys.matrices.ddm import DDM
  123. >>> A = DDM.from_list([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
  124. >>> A
  125. [[0, 1], [-1, 0]]
  126. >>> A == DDM([[ZZ(0), ZZ(1)], [ZZ(-1), ZZ(0)]], (2, 2), ZZ)
  127. True
  128. See Also
  129. ========
  130. from_list_flat
  131. """
  132. return cls(rowslist, shape, domain)
  133. @classmethod
  134. def from_ddm(cls, other):
  135. return other.copy()
  136. def to_list(self):
  137. """
  138. Convert to a list of lists.
  139. Examples
  140. ========
  141. >>> from sympy import QQ
  142. >>> from sympy.polys.matrices.ddm import DDM
  143. >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
  144. >>> A.to_list()
  145. [[1, 2], [3, 4]]
  146. See Also
  147. ========
  148. to_list_flat
  149. sympy.polys.matrices.domainmatrix.DomainMatrix.to_list
  150. """
  151. return [row[:] for row in self]
  152. def to_list_flat(self):
  153. """
  154. Convert to a flat list of elements.
  155. Examples
  156. ========
  157. >>> from sympy import QQ
  158. >>> from sympy.polys.matrices.ddm import DDM
  159. >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
  160. >>> A.to_list_flat()
  161. [1, 2, 3, 4]
  162. >>> A == DDM.from_list_flat(A.to_list_flat(), A.shape, A.domain)
  163. True
  164. See Also
  165. ========
  166. sympy.polys.matrices.domainmatrix.DomainMatrix.to_list_flat
  167. """
  168. flat = []
  169. for row in self:
  170. flat.extend(row)
  171. return flat
  172. @classmethod
  173. def from_list_flat(cls, flat, shape, domain):
  174. """
  175. Create a :class:`DDM` from a flat list of elements.
  176. Examples
  177. ========
  178. >>> from sympy import QQ
  179. >>> from sympy.polys.matrices.ddm import DDM
  180. >>> A = DDM.from_list_flat([1, 2, 3, 4], (2, 2), QQ)
  181. >>> A
  182. [[1, 2], [3, 4]]
  183. >>> A == DDM.from_list_flat(A.to_list_flat(), A.shape, A.domain)
  184. True
  185. See Also
  186. ========
  187. to_list_flat
  188. sympy.polys.matrices.domainmatrix.DomainMatrix.from_list_flat
  189. """
  190. assert type(flat) is list
  191. rows, cols = shape
  192. if not (len(flat) == rows*cols):
  193. raise DMBadInputError("Inconsistent flat-list shape")
  194. lol = [flat[i*cols:(i+1)*cols] for i in range(rows)]
  195. return cls(lol, shape, domain)
  196. def flatiter(self):
  197. return chain.from_iterable(self)
  198. def flat(self):
  199. items = []
  200. for row in self:
  201. items.extend(row)
  202. return items
  203. def to_flat_nz(self):
  204. """
  205. Convert to a flat list of nonzero elements and data.
  206. Explanation
  207. ===========
  208. This is used to operate on a list of the elements of a matrix and then
  209. reconstruct a matrix using :meth:`from_flat_nz`. Zero elements are
  210. included in the list but that may change in the future.
  211. Examples
  212. ========
  213. >>> from sympy.polys.matrices.ddm import DDM
  214. >>> from sympy import QQ
  215. >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
  216. >>> elements, data = A.to_flat_nz()
  217. >>> elements
  218. [1, 2, 3, 4]
  219. >>> A == DDM.from_flat_nz(elements, data, A.domain)
  220. True
  221. See Also
  222. ========
  223. from_flat_nz
  224. sympy.polys.matrices.sdm.SDM.to_flat_nz
  225. sympy.polys.matrices.domainmatrix.DomainMatrix.to_flat_nz
  226. """
  227. return self.to_sdm().to_flat_nz()
  228. @classmethod
  229. def from_flat_nz(cls, elements, data, domain):
  230. """
  231. Reconstruct a :class:`DDM` after calling :meth:`to_flat_nz`.
  232. Examples
  233. ========
  234. >>> from sympy.polys.matrices.ddm import DDM
  235. >>> from sympy import QQ
  236. >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
  237. >>> elements, data = A.to_flat_nz()
  238. >>> elements
  239. [1, 2, 3, 4]
  240. >>> A == DDM.from_flat_nz(elements, data, A.domain)
  241. True
  242. See Also
  243. ========
  244. to_flat_nz
  245. sympy.polys.matrices.sdm.SDM.from_flat_nz
  246. sympy.polys.matrices.domainmatrix.DomainMatrix.from_flat_nz
  247. """
  248. return SDM.from_flat_nz(elements, data, domain).to_ddm()
  249. def to_dod(self):
  250. """
  251. Convert to a dictionary of dictionaries (dod) format.
  252. Examples
  253. ========
  254. >>> from sympy.polys.matrices.ddm import DDM
  255. >>> from sympy import QQ
  256. >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
  257. >>> A.to_dod()
  258. {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}
  259. See Also
  260. ========
  261. from_dod
  262. sympy.polys.matrices.sdm.SDM.to_dod
  263. sympy.polys.matrices.domainmatrix.DomainMatrix.to_dod
  264. """
  265. dod = {}
  266. for i, row in enumerate(self):
  267. row = {j:e for j, e in enumerate(row) if e}
  268. if row:
  269. dod[i] = row
  270. return dod
  271. @classmethod
  272. def from_dod(cls, dod, shape, domain):
  273. """
  274. Create a :class:`DDM` from a dictionary of dictionaries (dod) format.
  275. Examples
  276. ========
  277. >>> from sympy.polys.matrices.ddm import DDM
  278. >>> from sympy import QQ
  279. >>> dod = {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}
  280. >>> A = DDM.from_dod(dod, (2, 2), QQ)
  281. >>> A
  282. [[1, 2], [3, 4]]
  283. See Also
  284. ========
  285. to_dod
  286. sympy.polys.matrices.sdm.SDM.from_dod
  287. sympy.polys.matrices.domainmatrix.DomainMatrix.from_dod
  288. """
  289. rows, cols = shape
  290. lol = [[domain.zero] * cols for _ in range(rows)]
  291. for i, row in dod.items():
  292. for j, element in row.items():
  293. lol[i][j] = element
  294. return DDM(lol, shape, domain)
  295. def to_dok(self):
  296. """
  297. Convert :class:`DDM` to dictionary of keys (dok) format.
  298. Examples
  299. ========
  300. >>> from sympy.polys.matrices.ddm import DDM
  301. >>> from sympy import QQ
  302. >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
  303. >>> A.to_dok()
  304. {(0, 0): 1, (0, 1): 2, (1, 0): 3, (1, 1): 4}
  305. See Also
  306. ========
  307. from_dok
  308. sympy.polys.matrices.sdm.SDM.to_dok
  309. sympy.polys.matrices.domainmatrix.DomainMatrix.to_dok
  310. """
  311. dok = {}
  312. for i, row in enumerate(self):
  313. for j, element in enumerate(row):
  314. if element:
  315. dok[i, j] = element
  316. return dok
  317. @classmethod
  318. def from_dok(cls, dok, shape, domain):
  319. """
  320. Create a :class:`DDM` from a dictionary of keys (dok) format.
  321. Examples
  322. ========
  323. >>> from sympy.polys.matrices.ddm import DDM
  324. >>> from sympy import QQ
  325. >>> dok = {(0, 0): 1, (0, 1): 2, (1, 0): 3, (1, 1): 4}
  326. >>> A = DDM.from_dok(dok, (2, 2), QQ)
  327. >>> A
  328. [[1, 2], [3, 4]]
  329. See Also
  330. ========
  331. to_dok
  332. sympy.polys.matrices.sdm.SDM.from_dok
  333. sympy.polys.matrices.domainmatrix.DomainMatrix.from_dok
  334. """
  335. rows, cols = shape
  336. lol = [[domain.zero] * cols for _ in range(rows)]
  337. for (i, j), element in dok.items():
  338. lol[i][j] = element
  339. return DDM(lol, shape, domain)
  340. def iter_values(self):
  341. """
  342. Iterate over the non-zero values of the matrix.
  343. Examples
  344. ========
  345. >>> from sympy.polys.matrices.ddm import DDM
  346. >>> from sympy import QQ
  347. >>> A = DDM([[QQ(1), QQ(0)], [QQ(3), QQ(4)]], (2, 2), QQ)
  348. >>> list(A.iter_values())
  349. [1, 3, 4]
  350. See Also
  351. ========
  352. iter_items
  353. to_list_flat
  354. sympy.polys.matrices.domainmatrix.DomainMatrix.iter_values
  355. """
  356. for row in self:
  357. yield from filter(None, row)
  358. def iter_items(self):
  359. """
  360. Iterate over indices and values of nonzero elements of the matrix.
  361. Examples
  362. ========
  363. >>> from sympy.polys.matrices.ddm import DDM
  364. >>> from sympy import QQ
  365. >>> A = DDM([[QQ(1), QQ(0)], [QQ(3), QQ(4)]], (2, 2), QQ)
  366. >>> list(A.iter_items())
  367. [((0, 0), 1), ((1, 0), 3), ((1, 1), 4)]
  368. See Also
  369. ========
  370. iter_values
  371. to_dok
  372. sympy.polys.matrices.domainmatrix.DomainMatrix.iter_items
  373. """
  374. for i, row in enumerate(self):
  375. for j, element in enumerate(row):
  376. if element:
  377. yield (i, j), element
  378. def to_ddm(self):
  379. """
  380. Convert to a :class:`DDM`.
  381. This just returns ``self`` but exists to parallel the corresponding
  382. method in other matrix types like :class:`~.SDM`.
  383. See Also
  384. ========
  385. to_sdm
  386. to_dfm
  387. to_dfm_or_ddm
  388. sympy.polys.matrices.sdm.SDM.to_ddm
  389. sympy.polys.matrices.domainmatrix.DomainMatrix.to_ddm
  390. """
  391. return self
  392. def to_sdm(self):
  393. """
  394. Convert to a :class:`~.SDM`.
  395. Examples
  396. ========
  397. >>> from sympy.polys.matrices.ddm import DDM
  398. >>> from sympy import QQ
  399. >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
  400. >>> A.to_sdm()
  401. {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}
  402. >>> type(A.to_sdm())
  403. <class 'sympy.polys.matrices.sdm.SDM'>
  404. See Also
  405. ========
  406. SDM
  407. sympy.polys.matrices.sdm.SDM.to_ddm
  408. """
  409. return SDM.from_list(self, self.shape, self.domain)
  410. @doctest_depends_on(ground_types=['flint'])
  411. def to_dfm(self):
  412. """
  413. Convert to :class:`~.DDM` to :class:`~.DFM`.
  414. Examples
  415. ========
  416. >>> from sympy.polys.matrices.ddm import DDM
  417. >>> from sympy import QQ
  418. >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
  419. >>> A.to_dfm()
  420. [[1, 2], [3, 4]]
  421. >>> type(A.to_dfm())
  422. <class 'sympy.polys.matrices._dfm.DFM'>
  423. See Also
  424. ========
  425. DFM
  426. sympy.polys.matrices._dfm.DFM.to_ddm
  427. """
  428. return DFM(list(self), self.shape, self.domain)
  429. @doctest_depends_on(ground_types=['flint'])
  430. def to_dfm_or_ddm(self):
  431. """
  432. Convert to :class:`~.DFM` if possible or otherwise return self.
  433. Examples
  434. ========
  435. >>> from sympy.polys.matrices.ddm import DDM
  436. >>> from sympy import QQ
  437. >>> A = DDM([[1, 2], [3, 4]], (2, 2), QQ)
  438. >>> A.to_dfm_or_ddm()
  439. [[1, 2], [3, 4]]
  440. >>> type(A.to_dfm_or_ddm())
  441. <class 'sympy.polys.matrices._dfm.DFM'>
  442. See Also
  443. ========
  444. to_dfm
  445. to_ddm
  446. sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm
  447. """
  448. if DFM._supports_domain(self.domain):
  449. return self.to_dfm()
  450. return self
  451. def convert_to(self, K):
  452. Kold = self.domain
  453. if K == Kold:
  454. return self.copy()
  455. rows = [[K.convert_from(e, Kold) for e in row] for row in self]
  456. return DDM(rows, self.shape, K)
  457. def __str__(self):
  458. rowsstr = ['[%s]' % ', '.join(map(str, row)) for row in self]
  459. return '[%s]' % ', '.join(rowsstr)
  460. def __repr__(self):
  461. cls = type(self).__name__
  462. rows = list.__repr__(self)
  463. return '%s(%s, %s, %s)' % (cls, rows, self.shape, self.domain)
  464. def __eq__(self, other):
  465. if not isinstance(other, DDM):
  466. return False
  467. return (super().__eq__(other) and self.domain == other.domain)
  468. def __ne__(self, other):
  469. return not self.__eq__(other)
  470. @classmethod
  471. def zeros(cls, shape, domain):
  472. z = domain.zero
  473. m, n = shape
  474. rowslist = [[z] * n for _ in range(m)]
  475. return DDM(rowslist, shape, domain)
  476. @classmethod
  477. def ones(cls, shape, domain):
  478. one = domain.one
  479. m, n = shape
  480. rowlist = [[one] * n for _ in range(m)]
  481. return DDM(rowlist, shape, domain)
  482. @classmethod
  483. def eye(cls, size, domain):
  484. if isinstance(size, tuple):
  485. m, n = size
  486. elif isinstance(size, int):
  487. m = n = size
  488. one = domain.one
  489. ddm = cls.zeros((m, n), domain)
  490. for i in range(min(m, n)):
  491. ddm[i][i] = one
  492. return ddm
  493. def copy(self):
  494. copyrows = [row[:] for row in self]
  495. return DDM(copyrows, self.shape, self.domain)
  496. def transpose(self):
  497. rows, cols = self.shape
  498. if rows:
  499. ddmT = ddm_transpose(self)
  500. else:
  501. ddmT = [[]] * cols
  502. return DDM(ddmT, (cols, rows), self.domain)
  503. def __add__(a, b):
  504. if not isinstance(b, DDM):
  505. return NotImplemented
  506. return a.add(b)
  507. def __sub__(a, b):
  508. if not isinstance(b, DDM):
  509. return NotImplemented
  510. return a.sub(b)
  511. def __neg__(a):
  512. return a.neg()
  513. def __mul__(a, b):
  514. if b in a.domain:
  515. return a.mul(b)
  516. else:
  517. return NotImplemented
  518. def __rmul__(a, b):
  519. if b in a.domain:
  520. return a.mul(b)
  521. else:
  522. return NotImplemented
  523. def __matmul__(a, b):
  524. if isinstance(b, DDM):
  525. return a.matmul(b)
  526. else:
  527. return NotImplemented
  528. @classmethod
  529. def _check(cls, a, op, b, ashape, bshape):
  530. if a.domain != b.domain:
  531. msg = "Domain mismatch: %s %s %s" % (a.domain, op, b.domain)
  532. raise DMDomainError(msg)
  533. if ashape != bshape:
  534. msg = "Shape mismatch: %s %s %s" % (a.shape, op, b.shape)
  535. raise DMShapeError(msg)
  536. def add(a, b):
  537. """a + b"""
  538. a._check(a, '+', b, a.shape, b.shape)
  539. c = a.copy()
  540. ddm_iadd(c, b)
  541. return c
  542. def sub(a, b):
  543. """a - b"""
  544. a._check(a, '-', b, a.shape, b.shape)
  545. c = a.copy()
  546. ddm_isub(c, b)
  547. return c
  548. def neg(a):
  549. """-a"""
  550. b = a.copy()
  551. ddm_ineg(b)
  552. return b
  553. def mul(a, b):
  554. c = a.copy()
  555. ddm_imul(c, b)
  556. return c
  557. def rmul(a, b):
  558. c = a.copy()
  559. ddm_irmul(c, b)
  560. return c
  561. def matmul(a, b):
  562. """a @ b (matrix product)"""
  563. m, o = a.shape
  564. o2, n = b.shape
  565. a._check(a, '*', b, o, o2)
  566. c = a.zeros((m, n), a.domain)
  567. ddm_imatmul(c, a, b)
  568. return c
  569. def mul_elementwise(a, b):
  570. assert a.shape == b.shape
  571. assert a.domain == b.domain
  572. c = [[aij * bij for aij, bij in zip(ai, bi)] for ai, bi in zip(a, b)]
  573. return DDM(c, a.shape, a.domain)
  574. def hstack(A, *B):
  575. """Horizontally stacks :py:class:`~.DDM` matrices.
  576. Examples
  577. ========
  578. >>> from sympy import ZZ
  579. >>> from sympy.polys.matrices.sdm import DDM
  580. >>> A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
  581. >>> B = DDM([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
  582. >>> A.hstack(B)
  583. [[1, 2, 5, 6], [3, 4, 7, 8]]
  584. >>> C = DDM([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
  585. >>> A.hstack(B, C)
  586. [[1, 2, 5, 6, 9, 10], [3, 4, 7, 8, 11, 12]]
  587. """
  588. Anew = list(A.copy())
  589. rows, cols = A.shape
  590. domain = A.domain
  591. for Bk in B:
  592. Bkrows, Bkcols = Bk.shape
  593. assert Bkrows == rows
  594. assert Bk.domain == domain
  595. cols += Bkcols
  596. for i, Bki in enumerate(Bk):
  597. Anew[i].extend(Bki)
  598. return DDM(Anew, (rows, cols), A.domain)
  599. def vstack(A, *B):
  600. """Vertically stacks :py:class:`~.DDM` matrices.
  601. Examples
  602. ========
  603. >>> from sympy import ZZ
  604. >>> from sympy.polys.matrices.sdm import DDM
  605. >>> A = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
  606. >>> B = DDM([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
  607. >>> A.vstack(B)
  608. [[1, 2], [3, 4], [5, 6], [7, 8]]
  609. >>> C = DDM([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
  610. >>> A.vstack(B, C)
  611. [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]]
  612. """
  613. Anew = list(A.copy())
  614. rows, cols = A.shape
  615. domain = A.domain
  616. for Bk in B:
  617. Bkrows, Bkcols = Bk.shape
  618. assert Bkcols == cols
  619. assert Bk.domain == domain
  620. rows += Bkrows
  621. Anew.extend(Bk.copy())
  622. return DDM(Anew, (rows, cols), A.domain)
  623. def applyfunc(self, func, domain):
  624. elements = [list(map(func, row)) for row in self]
  625. return DDM(elements, self.shape, domain)
  626. def nnz(a):
  627. """Number of non-zero entries in :py:class:`~.DDM` matrix.
  628. See Also
  629. ========
  630. sympy.polys.matrices.domainmatrix.DomainMatrix.nnz
  631. """
  632. return sum(sum(map(bool, row)) for row in a)
  633. def scc(a):
  634. """Strongly connected components of a square matrix *a*.
  635. Examples
  636. ========
  637. >>> from sympy import ZZ
  638. >>> from sympy.polys.matrices.sdm import DDM
  639. >>> A = DDM([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(1)]], (2, 2), ZZ)
  640. >>> A.scc()
  641. [[0], [1]]
  642. See also
  643. ========
  644. sympy.polys.matrices.domainmatrix.DomainMatrix.scc
  645. """
  646. return a.to_sdm().scc()
  647. @classmethod
  648. def diag(cls, values, domain):
  649. """Returns a square diagonal matrix with *values* on the diagonal.
  650. Examples
  651. ========
  652. >>> from sympy import ZZ
  653. >>> from sympy.polys.matrices.sdm import DDM
  654. >>> DDM.diag([ZZ(1), ZZ(2), ZZ(3)], ZZ)
  655. [[1, 0, 0], [0, 2, 0], [0, 0, 3]]
  656. See also
  657. ========
  658. sympy.polys.matrices.domainmatrix.DomainMatrix.diag
  659. """
  660. return SDM.diag(values, domain).to_ddm()
  661. def rref(a):
  662. """Reduced-row echelon form of a and list of pivots.
  663. See Also
  664. ========
  665. sympy.polys.matrices.domainmatrix.DomainMatrix.rref
  666. Higher level interface to this function.
  667. sympy.polys.matrices.dense.ddm_irref
  668. The underlying algorithm.
  669. """
  670. b = a.copy()
  671. K = a.domain
  672. partial_pivot = K.is_RealField or K.is_ComplexField
  673. pivots = ddm_irref(b, _partial_pivot=partial_pivot)
  674. return b, pivots
  675. def rref_den(a):
  676. """Reduced-row echelon form of a with denominator and list of pivots
  677. See Also
  678. ========
  679. sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den
  680. Higher level interface to this function.
  681. sympy.polys.matrices.dense.ddm_irref_den
  682. The underlying algorithm.
  683. """
  684. b = a.copy()
  685. K = a.domain
  686. denom, pivots = ddm_irref_den(b, K)
  687. return b, denom, pivots
  688. def nullspace(a):
  689. """Returns a basis for the nullspace of a.
  690. The domain of the matrix must be a field.
  691. See Also
  692. ========
  693. rref
  694. sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace
  695. """
  696. rref, pivots = a.rref()
  697. return rref.nullspace_from_rref(pivots)
  698. def nullspace_from_rref(a, pivots=None):
  699. """Compute the nullspace of a matrix from its rref.
  700. The domain of the matrix can be any domain.
  701. Returns a tuple (basis, nonpivots).
  702. See Also
  703. ========
  704. sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace
  705. The higher level interface to this function.
  706. """
  707. m, n = a.shape
  708. K = a.domain
  709. if pivots is None:
  710. pivots = []
  711. last_pivot = -1
  712. for i in range(m):
  713. ai = a[i]
  714. for j in range(last_pivot+1, n):
  715. if ai[j]:
  716. last_pivot = j
  717. pivots.append(j)
  718. break
  719. if not pivots:
  720. return (a.eye(n, K), list(range(n)))
  721. # After rref the pivots are all one but after rref_den they may not be.
  722. pivot_val = a[0][pivots[0]]
  723. basis = []
  724. nonpivots = []
  725. for i in range(n):
  726. if i in pivots:
  727. continue
  728. nonpivots.append(i)
  729. vec = [pivot_val if i == j else K.zero for j in range(n)]
  730. for ii, jj in enumerate(pivots):
  731. vec[jj] -= a[ii][i]
  732. basis.append(vec)
  733. basis_ddm = DDM(basis, (len(basis), n), K)
  734. return (basis_ddm, nonpivots)
  735. def particular(a):
  736. return a.to_sdm().particular().to_ddm()
  737. def det(a):
  738. """Determinant of a"""
  739. m, n = a.shape
  740. if m != n:
  741. raise DMNonSquareMatrixError("Determinant of non-square matrix")
  742. b = a.copy()
  743. K = b.domain
  744. deta = ddm_idet(b, K)
  745. return deta
  746. def inv(a):
  747. """Inverse of a"""
  748. m, n = a.shape
  749. if m != n:
  750. raise DMNonSquareMatrixError("Determinant of non-square matrix")
  751. ainv = a.copy()
  752. K = a.domain
  753. ddm_iinv(ainv, a, K)
  754. return ainv
  755. def lu(a):
  756. """L, U decomposition of a"""
  757. m, n = a.shape
  758. K = a.domain
  759. U = a.copy()
  760. L = a.eye(m, K)
  761. swaps = ddm_ilu_split(L, U, K)
  762. return L, U, swaps
  763. def _fflu(self):
  764. """
  765. Private method for Phase 1 of fraction-free LU decomposition.
  766. Performs row operations and elimination to compute U and permutation indices.
  767. Returns:
  768. LU : decomposition as a single matrix.
  769. perm (list): Permutation indices for row swaps.
  770. """
  771. rows, cols = self.shape
  772. K = self.domain
  773. LU = self.copy()
  774. perm = list(range(rows))
  775. rank = 0
  776. for j in range(min(rows, cols)):
  777. # Skip columns where all entries are zero
  778. if all(LU[i][j] == K.zero for i in range(rows)):
  779. continue
  780. # Find the first non-zero pivot in the current column
  781. pivot_row = -1
  782. for i in range(rank, rows):
  783. if LU[i][j] != K.zero:
  784. pivot_row = i
  785. break
  786. # If no pivot is found, skip column
  787. if pivot_row == -1:
  788. continue
  789. # Swap rows to bring the pivot to the current rank
  790. if pivot_row != rank:
  791. LU[rank], LU[pivot_row] = LU[pivot_row], LU[rank]
  792. perm[rank], perm[pivot_row] = perm[pivot_row], perm[rank]
  793. # Found pivot - (Gauss-Bareiss elimination)
  794. pivot = LU[rank][j]
  795. for i in range(rank + 1, rows):
  796. multiplier = LU[i][j]
  797. # Denominator is previous pivot or 1
  798. denominator = LU[rank - 1][rank - 1] if rank > 0 else K.one
  799. for k in range(j + 1, cols):
  800. LU[i][k] = K.exquo(pivot * LU[i][k] - LU[rank][k] * multiplier, denominator)
  801. # Keep the multiplier for L matrix
  802. LU[i][j] = multiplier
  803. rank += 1
  804. return LU, perm
  805. def fflu(self):
  806. """
  807. Fraction-free LU decomposition of DDM.
  808. See Also
  809. ========
  810. sympy.polys.matrices.domainmatrix.DomainMatrix.fflu
  811. The higher-level interface to this function.
  812. """
  813. rows, cols = self.shape
  814. K = self.domain
  815. # Phase 1: Perform row operations and get permutation
  816. U, perm = self._fflu()
  817. # Phase 2: Construct P, L, D matrices
  818. # Create P from permutation
  819. P = self.zeros((rows, rows), K)
  820. for i, pi in enumerate(perm):
  821. P[i][pi] = K.one
  822. # Create L matrix
  823. L = self.zeros((rows, rows), K)
  824. i = j = 0
  825. while i < rows and j < cols:
  826. if U[i][j] != K.zero:
  827. # Found non-zero pivot
  828. # Diagonal entry is the pivot
  829. L[i][i] = U[i][j]
  830. for l in range(i + 1, rows):
  831. # Off-diagonal entries are the multipliers
  832. L[l][i] = U[l][j]
  833. # zero out the entries in U
  834. U[l][j] = K.zero
  835. i += 1
  836. j += 1
  837. # Fill remaining diagonal of L with ones
  838. for i in range(i, rows):
  839. L[i][i] = K.one
  840. # Create D matrix - using FLINT's approach with accumulator
  841. D = self.zeros((rows, rows), K)
  842. if rows >= 1:
  843. D[0][0] = L[0][0]
  844. di = K.one
  845. for i in range(1, rows):
  846. # Accumulate product of pivots
  847. di = L[i - 1][i - 1] * L[i][i]
  848. D[i][i] = di
  849. return P, L, D, U
  850. def qr(self):
  851. """
  852. QR decomposition for DDM.
  853. Returns:
  854. - Q: Orthogonal matrix as a DDM.
  855. - R: Upper triangular matrix as a DDM.
  856. See Also
  857. ========
  858. sympy.polys.matrices.domainmatrix.DomainMatrix.qr
  859. The higher-level interface to this function.
  860. """
  861. rows, cols = self.shape
  862. K = self.domain
  863. Q = self.copy()
  864. R = self.zeros((min(rows, cols), cols), K)
  865. # Check that the domain is a field
  866. if not K.is_Field:
  867. raise DMDomainError("QR decomposition requires a field (e.g. QQ).")
  868. dot_cols = lambda i, j: K.sum(Q[k][i] * Q[k][j] for k in range(rows))
  869. for j in range(cols):
  870. for i in range(min(j, rows)):
  871. dot_ii = dot_cols(i, i)
  872. if dot_ii != K.zero:
  873. R[i][j] = dot_cols(i, j) / dot_ii
  874. for k in range(rows):
  875. Q[k][j] -= R[i][j] * Q[k][i]
  876. if j < rows:
  877. dot_jj = dot_cols(j, j)
  878. if dot_jj != K.zero:
  879. R[j][j] = K.one
  880. Q = Q.extract(range(rows), range(min(rows, cols)))
  881. return Q, R
  882. def lu_solve(a, b):
  883. """x where a*x = b"""
  884. m, n = a.shape
  885. m2, o = b.shape
  886. a._check(a, 'lu_solve', b, m, m2)
  887. if not a.domain.is_Field:
  888. raise DMDomainError("lu_solve requires a field")
  889. L, U, swaps = a.lu()
  890. x = a.zeros((n, o), a.domain)
  891. ddm_ilu_solve(x, L, U, swaps, b)
  892. return x
  893. def charpoly(a):
  894. """Coefficients of characteristic polynomial of a"""
  895. K = a.domain
  896. m, n = a.shape
  897. if m != n:
  898. raise DMNonSquareMatrixError("Charpoly of non-square matrix")
  899. vec = ddm_berk(a, K)
  900. coeffs = [vec[i][0] for i in range(n+1)]
  901. return coeffs
  902. def is_zero_matrix(self):
  903. """
  904. Says whether this matrix has all zero entries.
  905. """
  906. zero = self.domain.zero
  907. return all(Mij == zero for Mij in self.flatiter())
  908. def is_upper(self):
  909. """
  910. Says whether this matrix is upper-triangular. True can be returned
  911. even if the matrix is not square.
  912. """
  913. zero = self.domain.zero
  914. return all(Mij == zero for i, Mi in enumerate(self) for Mij in Mi[:i])
  915. def is_lower(self):
  916. """
  917. Says whether this matrix is lower-triangular. True can be returned
  918. even if the matrix is not square.
  919. """
  920. zero = self.domain.zero
  921. return all(Mij == zero for i, Mi in enumerate(self) for Mij in Mi[i+1:])
  922. def is_diagonal(self):
  923. """
  924. Says whether this matrix is diagonal. True can be returned even if
  925. the matrix is not square.
  926. """
  927. return self.is_upper() and self.is_lower()
  928. def diagonal(self):
  929. """
  930. Returns a list of the elements from the diagonal of the matrix.
  931. """
  932. m, n = self.shape
  933. return [self[i][i] for i in range(min(m, n))]
  934. def lll(A, delta=QQ(3, 4)):
  935. return ddm_lll(A, delta=delta)
  936. def lll_transform(A, delta=QQ(3, 4)):
  937. return ddm_lll_transform(A, delta=delta)
  938. from .sdm import SDM
  939. from .dfm import DFM