sdm.py 63 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197
  1. """
  2. Module for the SDM class.
  3. """
  4. from operator import add, neg, pos, sub, mul
  5. from collections import defaultdict
  6. from sympy.external.gmpy import GROUND_TYPES
  7. from sympy.utilities.decorator import doctest_depends_on
  8. from sympy.utilities.iterables import _strongly_connected_components
  9. from .exceptions import DMBadInputError, DMDomainError, DMShapeError
  10. from sympy.polys.domains import QQ
  11. from .ddm import DDM
  12. if GROUND_TYPES != 'flint':
  13. __doctest_skip__ = ['SDM.to_dfm', 'SDM.to_dfm_or_ddm']
  14. class SDM(dict):
  15. r"""Sparse matrix based on polys domain elements
  16. This is a dict subclass and is a wrapper for a dict of dicts that supports
  17. basic matrix arithmetic +, -, *, **.
  18. In order to create a new :py:class:`~.SDM`, a dict
  19. of dicts mapping non-zero elements to their
  20. corresponding row and column in the matrix is needed.
  21. We also need to specify the shape and :py:class:`~.Domain`
  22. of our :py:class:`~.SDM` object.
  23. We declare a 2x2 :py:class:`~.SDM` matrix belonging
  24. to QQ domain as shown below.
  25. The 2x2 Matrix in the example is
  26. .. math::
  27. A = \left[\begin{array}{ccc}
  28. 0 & \frac{1}{2} \\
  29. 0 & 0 \end{array} \right]
  30. >>> from sympy.polys.matrices.sdm import SDM
  31. >>> from sympy import QQ
  32. >>> elemsdict = {0:{1:QQ(1, 2)}}
  33. >>> A = SDM(elemsdict, (2, 2), QQ)
  34. >>> A
  35. {0: {1: 1/2}}
  36. We can manipulate :py:class:`~.SDM` the same way
  37. as a Matrix class
  38. >>> from sympy import ZZ
  39. >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
  40. >>> B = SDM({0:{0: ZZ(3)}, 1:{1:ZZ(4)}}, (2, 2), ZZ)
  41. >>> A + B
  42. {0: {0: 3, 1: 2}, 1: {0: 1, 1: 4}}
  43. Multiplication
  44. >>> A*B
  45. {0: {1: 8}, 1: {0: 3}}
  46. >>> A*ZZ(2)
  47. {0: {1: 4}, 1: {0: 2}}
  48. """
  49. fmt = 'sparse'
  50. is_DFM = False
  51. is_DDM = False
  52. def __init__(self, elemsdict, shape, domain):
  53. super().__init__(elemsdict)
  54. self.shape = self.rows, self.cols = m, n = shape
  55. self.domain = domain
  56. if not all(0 <= r < m for r in self):
  57. raise DMBadInputError("Row out of range")
  58. if not all(0 <= c < n for row in self.values() for c in row):
  59. raise DMBadInputError("Column out of range")
  60. def getitem(self, i, j):
  61. try:
  62. return self[i][j]
  63. except KeyError:
  64. m, n = self.shape
  65. if -m <= i < m and -n <= j < n:
  66. try:
  67. return self[i % m][j % n]
  68. except KeyError:
  69. return self.domain.zero
  70. else:
  71. raise IndexError("index out of range")
  72. def setitem(self, i, j, value):
  73. m, n = self.shape
  74. if not (-m <= i < m and -n <= j < n):
  75. raise IndexError("index out of range")
  76. i, j = i % m, j % n
  77. if value:
  78. try:
  79. self[i][j] = value
  80. except KeyError:
  81. self[i] = {j: value}
  82. else:
  83. rowi = self.get(i, None)
  84. if rowi is not None:
  85. try:
  86. del rowi[j]
  87. except KeyError:
  88. pass
  89. else:
  90. if not rowi:
  91. del self[i]
  92. def extract_slice(self, slice1, slice2):
  93. m, n = self.shape
  94. ri = range(m)[slice1]
  95. ci = range(n)[slice2]
  96. sdm = {}
  97. for i, row in self.items():
  98. if i in ri:
  99. row = {ci.index(j): e for j, e in row.items() if j in ci}
  100. if row:
  101. sdm[ri.index(i)] = row
  102. return self.new(sdm, (len(ri), len(ci)), self.domain)
  103. def extract(self, rows, cols):
  104. if not (self and rows and cols):
  105. return self.zeros((len(rows), len(cols)), self.domain)
  106. m, n = self.shape
  107. if not (-m <= min(rows) <= max(rows) < m):
  108. raise IndexError('Row index out of range')
  109. if not (-n <= min(cols) <= max(cols) < n):
  110. raise IndexError('Column index out of range')
  111. # rows and cols can contain duplicates e.g. M[[1, 2, 2], [0, 1]]
  112. # Build a map from row/col in self to list of rows/cols in output
  113. rowmap = defaultdict(list)
  114. colmap = defaultdict(list)
  115. for i2, i1 in enumerate(rows):
  116. rowmap[i1 % m].append(i2)
  117. for j2, j1 in enumerate(cols):
  118. colmap[j1 % n].append(j2)
  119. # Used to efficiently skip zero rows/cols
  120. rowset = set(rowmap)
  121. colset = set(colmap)
  122. sdm1 = self
  123. sdm2 = {}
  124. for i1 in rowset & sdm1.keys():
  125. row1 = sdm1[i1]
  126. row2 = {}
  127. for j1 in colset & row1.keys():
  128. row1_j1 = row1[j1]
  129. for j2 in colmap[j1]:
  130. row2[j2] = row1_j1
  131. if row2:
  132. for i2 in rowmap[i1]:
  133. sdm2[i2] = row2.copy()
  134. return self.new(sdm2, (len(rows), len(cols)), self.domain)
  135. def __str__(self):
  136. rowsstr = []
  137. for i, row in self.items():
  138. elemsstr = ', '.join('%s: %s' % (j, elem) for j, elem in row.items())
  139. rowsstr.append('%s: {%s}' % (i, elemsstr))
  140. return '{%s}' % ', '.join(rowsstr)
  141. def __repr__(self):
  142. cls = type(self).__name__
  143. rows = dict.__repr__(self)
  144. return '%s(%s, %s, %s)' % (cls, rows, self.shape, self.domain)
  145. @classmethod
  146. def new(cls, sdm, shape, domain):
  147. """
  148. Parameters
  149. ==========
  150. sdm: A dict of dicts for non-zero elements in SDM
  151. shape: tuple representing dimension of SDM
  152. domain: Represents :py:class:`~.Domain` of SDM
  153. Returns
  154. =======
  155. An :py:class:`~.SDM` object
  156. Examples
  157. ========
  158. >>> from sympy.polys.matrices.sdm import SDM
  159. >>> from sympy import QQ
  160. >>> elemsdict = {0:{1: QQ(2)}}
  161. >>> A = SDM.new(elemsdict, (2, 2), QQ)
  162. >>> A
  163. {0: {1: 2}}
  164. """
  165. return cls(sdm, shape, domain)
  166. def copy(A):
  167. """
  168. Returns the copy of a :py:class:`~.SDM` object
  169. Examples
  170. ========
  171. >>> from sympy.polys.matrices.sdm import SDM
  172. >>> from sympy import QQ
  173. >>> elemsdict = {0:{1:QQ(2)}, 1:{}}
  174. >>> A = SDM(elemsdict, (2, 2), QQ)
  175. >>> B = A.copy()
  176. >>> B
  177. {0: {1: 2}, 1: {}}
  178. """
  179. Ac = {i: Ai.copy() for i, Ai in A.items()}
  180. return A.new(Ac, A.shape, A.domain)
  181. @classmethod
  182. def from_list(cls, ddm, shape, domain):
  183. """
  184. Create :py:class:`~.SDM` object from a list of lists.
  185. Parameters
  186. ==========
  187. ddm:
  188. list of lists containing domain elements
  189. shape:
  190. Dimensions of :py:class:`~.SDM` matrix
  191. domain:
  192. Represents :py:class:`~.Domain` of :py:class:`~.SDM` object
  193. Returns
  194. =======
  195. :py:class:`~.SDM` containing elements of ddm
  196. Examples
  197. ========
  198. >>> from sympy.polys.matrices.sdm import SDM
  199. >>> from sympy import QQ
  200. >>> ddm = [[QQ(1, 2), QQ(0)], [QQ(0), QQ(3, 4)]]
  201. >>> A = SDM.from_list(ddm, (2, 2), QQ)
  202. >>> A
  203. {0: {0: 1/2}, 1: {1: 3/4}}
  204. See Also
  205. ========
  206. to_list
  207. from_list_flat
  208. from_dok
  209. from_ddm
  210. """
  211. m, n = shape
  212. if not (len(ddm) == m and all(len(row) == n for row in ddm)):
  213. raise DMBadInputError("Inconsistent row-list/shape")
  214. getrow = lambda i: {j:ddm[i][j] for j in range(n) if ddm[i][j]}
  215. irows = ((i, getrow(i)) for i in range(m))
  216. sdm = {i: row for i, row in irows if row}
  217. return cls(sdm, shape, domain)
  218. @classmethod
  219. def from_ddm(cls, ddm):
  220. """
  221. Create :py:class:`~.SDM` from a :py:class:`~.DDM`.
  222. Examples
  223. ========
  224. >>> from sympy.polys.matrices.ddm import DDM
  225. >>> from sympy.polys.matrices.sdm import SDM
  226. >>> from sympy import QQ
  227. >>> ddm = DDM( [[QQ(1, 2), 0], [0, QQ(3, 4)]], (2, 2), QQ)
  228. >>> A = SDM.from_ddm(ddm)
  229. >>> A
  230. {0: {0: 1/2}, 1: {1: 3/4}}
  231. >>> SDM.from_ddm(ddm).to_ddm() == ddm
  232. True
  233. See Also
  234. ========
  235. to_ddm
  236. from_list
  237. from_list_flat
  238. from_dok
  239. """
  240. return cls.from_list(ddm, ddm.shape, ddm.domain)
  241. def to_list(M):
  242. """
  243. Convert a :py:class:`~.SDM` object to a list of lists.
  244. Examples
  245. ========
  246. >>> from sympy.polys.matrices.sdm import SDM
  247. >>> from sympy import QQ
  248. >>> elemsdict = {0:{1:QQ(2)}, 1:{}}
  249. >>> A = SDM(elemsdict, (2, 2), QQ)
  250. >>> A.to_list()
  251. [[0, 2], [0, 0]]
  252. """
  253. m, n = M.shape
  254. zero = M.domain.zero
  255. ddm = [[zero] * n for _ in range(m)]
  256. for i, row in M.items():
  257. for j, e in row.items():
  258. ddm[i][j] = e
  259. return ddm
  260. def to_list_flat(M):
  261. """
  262. Convert :py:class:`~.SDM` to a flat list.
  263. Examples
  264. ========
  265. >>> from sympy.polys.matrices.sdm import SDM
  266. >>> from sympy import QQ
  267. >>> A = SDM({0:{1:QQ(2)}, 1:{0: QQ(3)}}, (2, 2), QQ)
  268. >>> A.to_list_flat()
  269. [0, 2, 3, 0]
  270. >>> A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain)
  271. True
  272. See Also
  273. ========
  274. from_list_flat
  275. to_list
  276. to_dok
  277. to_ddm
  278. """
  279. m, n = M.shape
  280. zero = M.domain.zero
  281. flat = [zero] * (m * n)
  282. for i, row in M.items():
  283. for j, e in row.items():
  284. flat[i*n + j] = e
  285. return flat
  286. @classmethod
  287. def from_list_flat(cls, elements, shape, domain):
  288. """
  289. Create :py:class:`~.SDM` from a flat list of elements.
  290. Examples
  291. ========
  292. >>> from sympy.polys.matrices.sdm import SDM
  293. >>> from sympy import QQ
  294. >>> A = SDM.from_list_flat([QQ(0), QQ(2), QQ(0), QQ(0)], (2, 2), QQ)
  295. >>> A
  296. {0: {1: 2}}
  297. >>> A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain)
  298. True
  299. See Also
  300. ========
  301. to_list_flat
  302. from_list
  303. from_dok
  304. from_ddm
  305. """
  306. m, n = shape
  307. if len(elements) != m * n:
  308. raise DMBadInputError("Inconsistent flat-list shape")
  309. sdm = defaultdict(dict)
  310. for inj, element in enumerate(elements):
  311. if element:
  312. i, j = divmod(inj, n)
  313. sdm[i][j] = element
  314. return cls(sdm, shape, domain)
  315. def to_flat_nz(M):
  316. """
  317. Convert :class:`SDM` to a flat list of nonzero elements and data.
  318. Explanation
  319. ===========
  320. This is used to operate on a list of the elements of a matrix and then
  321. reconstruct a modified matrix with elements in the same positions using
  322. :meth:`from_flat_nz`. Zero elements are omitted from the list.
  323. Examples
  324. ========
  325. >>> from sympy.polys.matrices.sdm import SDM
  326. >>> from sympy import QQ
  327. >>> A = SDM({0:{1:QQ(2)}, 1:{0: QQ(3)}}, (2, 2), QQ)
  328. >>> elements, data = A.to_flat_nz()
  329. >>> elements
  330. [2, 3]
  331. >>> A == A.from_flat_nz(elements, data, A.domain)
  332. True
  333. See Also
  334. ========
  335. from_flat_nz
  336. to_list_flat
  337. sympy.polys.matrices.ddm.DDM.to_flat_nz
  338. sympy.polys.matrices.domainmatrix.DomainMatrix.to_flat_nz
  339. """
  340. dok = M.to_dok()
  341. indices = tuple(dok)
  342. elements = list(dok.values())
  343. data = (indices, M.shape)
  344. return elements, data
  345. @classmethod
  346. def from_flat_nz(cls, elements, data, domain):
  347. """
  348. Reconstruct a :class:`~.SDM` after calling :meth:`to_flat_nz`.
  349. See :meth:`to_flat_nz` for explanation.
  350. See Also
  351. ========
  352. to_flat_nz
  353. from_list_flat
  354. sympy.polys.matrices.ddm.DDM.from_flat_nz
  355. sympy.polys.matrices.domainmatrix.DomainMatrix.from_flat_nz
  356. """
  357. indices, shape = data
  358. dok = dict(zip(indices, elements))
  359. return cls.from_dok(dok, shape, domain)
  360. def to_dod(M):
  361. """
  362. Convert to dictionary of dictionaries (dod) format.
  363. Examples
  364. ========
  365. >>> from sympy.polys.matrices.sdm import SDM
  366. >>> from sympy import QQ
  367. >>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ)
  368. >>> A.to_dod()
  369. {0: {1: 2}, 1: {0: 3}}
  370. See Also
  371. ========
  372. from_dod
  373. sympy.polys.matrices.domainmatrix.DomainMatrix.to_dod
  374. """
  375. return {i: row.copy() for i, row in M.items()}
  376. @classmethod
  377. def from_dod(cls, dod, shape, domain):
  378. """
  379. Create :py:class:`~.SDM` from dictionary of dictionaries (dod) format.
  380. Examples
  381. ========
  382. >>> from sympy.polys.matrices.sdm import SDM
  383. >>> from sympy import QQ
  384. >>> dod = {0: {1: QQ(2)}, 1: {0: QQ(3)}}
  385. >>> A = SDM.from_dod(dod, (2, 2), QQ)
  386. >>> A
  387. {0: {1: 2}, 1: {0: 3}}
  388. >>> A == SDM.from_dod(A.to_dod(), A.shape, A.domain)
  389. True
  390. See Also
  391. ========
  392. to_dod
  393. sympy.polys.matrices.domainmatrix.DomainMatrix.to_dod
  394. """
  395. sdm = defaultdict(dict)
  396. for i, row in dod.items():
  397. for j, e in row.items():
  398. if e:
  399. sdm[i][j] = e
  400. return cls(sdm, shape, domain)
  401. def to_dok(M):
  402. """
  403. Convert to dictionary of keys (dok) format.
  404. Examples
  405. ========
  406. >>> from sympy.polys.matrices.sdm import SDM
  407. >>> from sympy import QQ
  408. >>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ)
  409. >>> A.to_dok()
  410. {(0, 1): 2, (1, 0): 3}
  411. See Also
  412. ========
  413. from_dok
  414. to_list
  415. to_list_flat
  416. to_ddm
  417. """
  418. return {(i, j): e for i, row in M.items() for j, e in row.items()}
  419. @classmethod
  420. def from_dok(cls, dok, shape, domain):
  421. """
  422. Create :py:class:`~.SDM` from dictionary of keys (dok) format.
  423. Examples
  424. ========
  425. >>> from sympy.polys.matrices.sdm import SDM
  426. >>> from sympy import QQ
  427. >>> dok = {(0, 1): QQ(2), (1, 0): QQ(3)}
  428. >>> A = SDM.from_dok(dok, (2, 2), QQ)
  429. >>> A
  430. {0: {1: 2}, 1: {0: 3}}
  431. >>> A == SDM.from_dok(A.to_dok(), A.shape, A.domain)
  432. True
  433. See Also
  434. ========
  435. to_dok
  436. from_list
  437. from_list_flat
  438. from_ddm
  439. """
  440. sdm = defaultdict(dict)
  441. for (i, j), e in dok.items():
  442. if e:
  443. sdm[i][j] = e
  444. return cls(sdm, shape, domain)
  445. def iter_values(M):
  446. """
  447. Iterate over the nonzero values of a :py:class:`~.SDM` matrix.
  448. Examples
  449. ========
  450. >>> from sympy.polys.matrices.sdm import SDM
  451. >>> from sympy import QQ
  452. >>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ)
  453. >>> list(A.iter_values())
  454. [2, 3]
  455. """
  456. for row in M.values():
  457. yield from row.values()
  458. def iter_items(M):
  459. """
  460. Iterate over indices and values of the nonzero elements.
  461. Examples
  462. ========
  463. >>> from sympy.polys.matrices.sdm import SDM
  464. >>> from sympy import QQ
  465. >>> A = SDM({0: {1: QQ(2)}, 1: {0: QQ(3)}}, (2, 2), QQ)
  466. >>> list(A.iter_items())
  467. [((0, 1), 2), ((1, 0), 3)]
  468. See Also
  469. ========
  470. sympy.polys.matrices.domainmatrix.DomainMatrix.iter_items
  471. """
  472. for i, row in M.items():
  473. for j, e in row.items():
  474. yield (i, j), e
  475. def to_ddm(M):
  476. """
  477. Convert a :py:class:`~.SDM` object to a :py:class:`~.DDM` object
  478. Examples
  479. ========
  480. >>> from sympy.polys.matrices.sdm import SDM
  481. >>> from sympy import QQ
  482. >>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ)
  483. >>> A.to_ddm()
  484. [[0, 2], [0, 0]]
  485. """
  486. return DDM(M.to_list(), M.shape, M.domain)
  487. def to_sdm(M):
  488. """
  489. Convert to :py:class:`~.SDM` format (returns self).
  490. """
  491. return M
  492. @doctest_depends_on(ground_types=['flint'])
  493. def to_dfm(M):
  494. """
  495. Convert a :py:class:`~.SDM` object to a :py:class:`~.DFM` object
  496. Examples
  497. ========
  498. >>> from sympy.polys.matrices.sdm import SDM
  499. >>> from sympy import QQ
  500. >>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ)
  501. >>> A.to_dfm()
  502. [[0, 2], [0, 0]]
  503. See Also
  504. ========
  505. to_ddm
  506. to_dfm_or_ddm
  507. sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm
  508. """
  509. return M.to_ddm().to_dfm()
  510. @doctest_depends_on(ground_types=['flint'])
  511. def to_dfm_or_ddm(M):
  512. """
  513. Convert to :py:class:`~.DFM` if possible, else :py:class:`~.DDM`.
  514. Examples
  515. ========
  516. >>> from sympy.polys.matrices.sdm import SDM
  517. >>> from sympy import QQ
  518. >>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ)
  519. >>> A.to_dfm_or_ddm()
  520. [[0, 2], [0, 0]]
  521. >>> type(A.to_dfm_or_ddm()) # depends on the ground types
  522. <class 'sympy.polys.matrices._dfm.DFM'>
  523. See Also
  524. ========
  525. to_ddm
  526. to_dfm
  527. sympy.polys.matrices.domainmatrix.DomainMatrix.to_dfm_or_ddm
  528. """
  529. return M.to_ddm().to_dfm_or_ddm()
  530. @classmethod
  531. def zeros(cls, shape, domain):
  532. r"""
  533. Returns a :py:class:`~.SDM` of size shape,
  534. belonging to the specified domain
  535. In the example below we declare a matrix A where,
  536. .. math::
  537. A := \left[\begin{array}{ccc}
  538. 0 & 0 & 0 \\
  539. 0 & 0 & 0 \end{array} \right]
  540. >>> from sympy.polys.matrices.sdm import SDM
  541. >>> from sympy import QQ
  542. >>> A = SDM.zeros((2, 3), QQ)
  543. >>> A
  544. {}
  545. """
  546. return cls({}, shape, domain)
  547. @classmethod
  548. def ones(cls, shape, domain):
  549. one = domain.one
  550. m, n = shape
  551. row = dict(zip(range(n), [one]*n))
  552. sdm = {i: row.copy() for i in range(m)}
  553. return cls(sdm, shape, domain)
  554. @classmethod
  555. def eye(cls, shape, domain):
  556. """
  557. Returns a identity :py:class:`~.SDM` matrix of dimensions
  558. size x size, belonging to the specified domain
  559. Examples
  560. ========
  561. >>> from sympy.polys.matrices.sdm import SDM
  562. >>> from sympy import QQ
  563. >>> I = SDM.eye((2, 2), QQ)
  564. >>> I
  565. {0: {0: 1}, 1: {1: 1}}
  566. """
  567. if isinstance(shape, int):
  568. rows, cols = shape, shape
  569. else:
  570. rows, cols = shape
  571. one = domain.one
  572. sdm = {i: {i: one} for i in range(min(rows, cols))}
  573. return cls(sdm, (rows, cols), domain)
  574. @classmethod
  575. def diag(cls, diagonal, domain, shape=None):
  576. if shape is None:
  577. shape = (len(diagonal), len(diagonal))
  578. sdm = {i: {i: v} for i, v in enumerate(diagonal) if v}
  579. return cls(sdm, shape, domain)
  580. def transpose(M):
  581. """
  582. Returns the transpose of a :py:class:`~.SDM` matrix
  583. Examples
  584. ========
  585. >>> from sympy.polys.matrices.sdm import SDM
  586. >>> from sympy import QQ
  587. >>> A = SDM({0:{1:QQ(2)}, 1:{}}, (2, 2), QQ)
  588. >>> A.transpose()
  589. {1: {0: 2}}
  590. """
  591. MT = sdm_transpose(M)
  592. return M.new(MT, M.shape[::-1], M.domain)
  593. def __add__(A, B):
  594. if not isinstance(B, SDM):
  595. return NotImplemented
  596. elif A.shape != B.shape:
  597. raise DMShapeError("Matrix size mismatch: %s + %s" % (A.shape, B.shape))
  598. return A.add(B)
  599. def __sub__(A, B):
  600. if not isinstance(B, SDM):
  601. return NotImplemented
  602. elif A.shape != B.shape:
  603. raise DMShapeError("Matrix size mismatch: %s - %s" % (A.shape, B.shape))
  604. return A.sub(B)
  605. def __neg__(A):
  606. return A.neg()
  607. def __mul__(A, B):
  608. """A * B"""
  609. if isinstance(B, SDM):
  610. return A.matmul(B)
  611. elif B in A.domain:
  612. return A.mul(B)
  613. else:
  614. return NotImplemented
  615. def __rmul__(a, b):
  616. if b in a.domain:
  617. return a.rmul(b)
  618. else:
  619. return NotImplemented
  620. def matmul(A, B):
  621. """
  622. Performs matrix multiplication of two SDM matrices
  623. Parameters
  624. ==========
  625. A, B: SDM to multiply
  626. Returns
  627. =======
  628. SDM
  629. SDM after multiplication
  630. Raises
  631. ======
  632. DomainError
  633. If domain of A does not match
  634. with that of B
  635. Examples
  636. ========
  637. >>> from sympy import ZZ
  638. >>> from sympy.polys.matrices.sdm import SDM
  639. >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
  640. >>> B = SDM({0:{0:ZZ(2), 1:ZZ(3)}, 1:{0:ZZ(4)}}, (2, 2), ZZ)
  641. >>> A.matmul(B)
  642. {0: {0: 8}, 1: {0: 2, 1: 3}}
  643. """
  644. if A.domain != B.domain:
  645. raise DMDomainError
  646. m, n = A.shape
  647. n2, o = B.shape
  648. if n != n2:
  649. raise DMShapeError
  650. C = sdm_matmul(A, B, A.domain, m, o)
  651. return A.new(C, (m, o), A.domain)
  652. def mul(A, b):
  653. """
  654. Multiplies each element of A with a scalar b
  655. Examples
  656. ========
  657. >>> from sympy import ZZ
  658. >>> from sympy.polys.matrices.sdm import SDM
  659. >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
  660. >>> A.mul(ZZ(3))
  661. {0: {1: 6}, 1: {0: 3}}
  662. """
  663. Csdm = unop_dict(A, lambda aij: aij*b)
  664. return A.new(Csdm, A.shape, A.domain)
  665. def rmul(A, b):
  666. Csdm = unop_dict(A, lambda aij: b*aij)
  667. return A.new(Csdm, A.shape, A.domain)
  668. def mul_elementwise(A, B):
  669. if A.domain != B.domain:
  670. raise DMDomainError
  671. if A.shape != B.shape:
  672. raise DMShapeError
  673. zero = A.domain.zero
  674. fzero = lambda e: zero
  675. Csdm = binop_dict(A, B, mul, fzero, fzero)
  676. return A.new(Csdm, A.shape, A.domain)
  677. def add(A, B):
  678. """
  679. Adds two :py:class:`~.SDM` matrices
  680. Examples
  681. ========
  682. >>> from sympy import ZZ
  683. >>> from sympy.polys.matrices.sdm import SDM
  684. >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
  685. >>> B = SDM({0:{0: ZZ(3)}, 1:{1:ZZ(4)}}, (2, 2), ZZ)
  686. >>> A.add(B)
  687. {0: {0: 3, 1: 2}, 1: {0: 1, 1: 4}}
  688. """
  689. Csdm = binop_dict(A, B, add, pos, pos)
  690. return A.new(Csdm, A.shape, A.domain)
  691. def sub(A, B):
  692. """
  693. Subtracts two :py:class:`~.SDM` matrices
  694. Examples
  695. ========
  696. >>> from sympy import ZZ
  697. >>> from sympy.polys.matrices.sdm import SDM
  698. >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
  699. >>> B = SDM({0:{0: ZZ(3)}, 1:{1:ZZ(4)}}, (2, 2), ZZ)
  700. >>> A.sub(B)
  701. {0: {0: -3, 1: 2}, 1: {0: 1, 1: -4}}
  702. """
  703. Csdm = binop_dict(A, B, sub, pos, neg)
  704. return A.new(Csdm, A.shape, A.domain)
  705. def neg(A):
  706. """
  707. Returns the negative of a :py:class:`~.SDM` matrix
  708. Examples
  709. ========
  710. >>> from sympy import ZZ
  711. >>> from sympy.polys.matrices.sdm import SDM
  712. >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
  713. >>> A.neg()
  714. {0: {1: -2}, 1: {0: -1}}
  715. """
  716. Csdm = unop_dict(A, neg)
  717. return A.new(Csdm, A.shape, A.domain)
  718. def convert_to(A, K):
  719. """
  720. Converts the :py:class:`~.Domain` of a :py:class:`~.SDM` matrix to K
  721. Examples
  722. ========
  723. >>> from sympy import ZZ, QQ
  724. >>> from sympy.polys.matrices.sdm import SDM
  725. >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
  726. >>> A.convert_to(QQ)
  727. {0: {1: 2}, 1: {0: 1}}
  728. """
  729. Kold = A.domain
  730. if K == Kold:
  731. return A.copy()
  732. Ak = unop_dict(A, lambda e: K.convert_from(e, Kold))
  733. return A.new(Ak, A.shape, K)
  734. def nnz(A):
  735. """Number of non-zero elements in the :py:class:`~.SDM` matrix.
  736. Examples
  737. ========
  738. >>> from sympy import ZZ
  739. >>> from sympy.polys.matrices.sdm import SDM
  740. >>> A = SDM({0:{1: ZZ(2)}, 1:{0:ZZ(1)}}, (2, 2), ZZ)
  741. >>> A.nnz()
  742. 2
  743. See Also
  744. ========
  745. sympy.polys.matrices.domainmatrix.DomainMatrix.nnz
  746. """
  747. return sum(map(len, A.values()))
  748. def scc(A):
  749. """Strongly connected components of a square matrix *A*.
  750. Examples
  751. ========
  752. >>> from sympy import ZZ
  753. >>> from sympy.polys.matrices.sdm import SDM
  754. >>> A = SDM({0:{0: ZZ(2)}, 1:{1:ZZ(1)}}, (2, 2), ZZ)
  755. >>> A.scc()
  756. [[0], [1]]
  757. See also
  758. ========
  759. sympy.polys.matrices.domainmatrix.DomainMatrix.scc
  760. """
  761. rows, cols = A.shape
  762. assert rows == cols
  763. V = range(rows)
  764. Emap = {v: list(A.get(v, [])) for v in V}
  765. return _strongly_connected_components(V, Emap)
  766. def rref(A):
  767. """
  768. Returns reduced-row echelon form and list of pivots for the :py:class:`~.SDM`
  769. Examples
  770. ========
  771. >>> from sympy import QQ
  772. >>> from sympy.polys.matrices.sdm import SDM
  773. >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(2), 1:QQ(4)}}, (2, 2), QQ)
  774. >>> A.rref()
  775. ({0: {0: 1, 1: 2}}, [0])
  776. """
  777. B, pivots, _ = sdm_irref(A)
  778. return A.new(B, A.shape, A.domain), pivots
  779. def rref_den(A):
  780. """
  781. Returns reduced-row echelon form (RREF) with denominator and pivots.
  782. Examples
  783. ========
  784. >>> from sympy import QQ
  785. >>> from sympy.polys.matrices.sdm import SDM
  786. >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(2), 1:QQ(4)}}, (2, 2), QQ)
  787. >>> A.rref_den()
  788. ({0: {0: 1, 1: 2}}, 1, [0])
  789. """
  790. K = A.domain
  791. A_rref_sdm, denom, pivots = sdm_rref_den(A, K)
  792. A_rref = A.new(A_rref_sdm, A.shape, A.domain)
  793. return A_rref, denom, pivots
  794. def inv(A):
  795. """
  796. Returns inverse of a matrix A
  797. Examples
  798. ========
  799. >>> from sympy import QQ
  800. >>> from sympy.polys.matrices.sdm import SDM
  801. >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
  802. >>> A.inv()
  803. {0: {0: -2, 1: 1}, 1: {0: 3/2, 1: -1/2}}
  804. """
  805. return A.to_dfm_or_ddm().inv().to_sdm()
  806. def det(A):
  807. """
  808. Returns determinant of A
  809. Examples
  810. ========
  811. >>> from sympy import QQ
  812. >>> from sympy.polys.matrices.sdm import SDM
  813. >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
  814. >>> A.det()
  815. -2
  816. """
  817. # It would be better to have a sparse implementation of det for use
  818. # with very sparse matrices. Extremely sparse matrices probably just
  819. # have determinant zero and we could probably detect that very quickly.
  820. # In the meantime, we convert to a dense matrix and use ddm_idet.
  821. #
  822. # If GROUND_TYPES=flint though then we will use Flint's implementation
  823. # if possible (dfm).
  824. return A.to_dfm_or_ddm().det()
  825. def lu(A):
  826. """
  827. Returns LU decomposition for a matrix A
  828. Examples
  829. ========
  830. >>> from sympy import QQ
  831. >>> from sympy.polys.matrices.sdm import SDM
  832. >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
  833. >>> A.lu()
  834. ({0: {0: 1}, 1: {0: 3, 1: 1}}, {0: {0: 1, 1: 2}, 1: {1: -2}}, [])
  835. """
  836. L, U, swaps = A.to_ddm().lu()
  837. return A.from_ddm(L), A.from_ddm(U), swaps
  838. def qr(self):
  839. """
  840. QR decomposition for SDM (Sparse Domain Matrix).
  841. Returns:
  842. - Q: Orthogonal matrix as a SDM.
  843. - R: Upper triangular matrix as a SDM.
  844. """
  845. ddm_q, ddm_r = self.to_ddm().qr()
  846. Q = ddm_q.to_sdm()
  847. R = ddm_r.to_sdm()
  848. return Q, R
  849. def lu_solve(A, b):
  850. """
  851. Uses LU decomposition to solve Ax = b,
  852. Examples
  853. ========
  854. >>> from sympy import QQ
  855. >>> from sympy.polys.matrices.sdm import SDM
  856. >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
  857. >>> b = SDM({0:{0:QQ(1)}, 1:{0:QQ(2)}}, (2, 1), QQ)
  858. >>> A.lu_solve(b)
  859. {1: {0: 1/2}}
  860. """
  861. return A.from_ddm(A.to_ddm().lu_solve(b.to_ddm()))
  862. def fflu(self):
  863. """
  864. Fraction free LU decomposition of SDM.
  865. Uses DDM implementation.
  866. See Also
  867. ========
  868. sympy.polys.matrices.ddm.DDM.fflu
  869. """
  870. ddm_p, ddm_l, ddm_d, ddm_u = self.to_dfm_or_ddm().fflu()
  871. P = ddm_p.to_sdm()
  872. L = ddm_l.to_sdm()
  873. D = ddm_d.to_sdm()
  874. U = ddm_u.to_sdm()
  875. return P, L, D, U
  876. def nullspace(A):
  877. """
  878. Nullspace of a :py:class:`~.SDM` matrix A.
  879. The domain of the matrix must be a field.
  880. It is better to use the :meth:`~.DomainMatrix.nullspace` method rather
  881. than this method which is otherwise no longer used.
  882. Examples
  883. ========
  884. >>> from sympy import QQ
  885. >>> from sympy.polys.matrices.sdm import SDM
  886. >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0: QQ(2), 1: QQ(4)}}, (2, 2), QQ)
  887. >>> A.nullspace()
  888. ({0: {0: -2, 1: 1}}, [1])
  889. See Also
  890. ========
  891. sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace
  892. The preferred way to get the nullspace of a matrix.
  893. """
  894. ncols = A.shape[1]
  895. one = A.domain.one
  896. B, pivots, nzcols = sdm_irref(A)
  897. K, nonpivots = sdm_nullspace_from_rref(B, one, ncols, pivots, nzcols)
  898. K = dict(enumerate(K))
  899. shape = (len(K), ncols)
  900. return A.new(K, shape, A.domain), nonpivots
  901. def nullspace_from_rref(A, pivots=None):
  902. """
  903. Returns nullspace for a :py:class:`~.SDM` matrix ``A`` in RREF.
  904. The domain of the matrix can be any domain.
  905. The matrix must already be in reduced row echelon form (RREF).
  906. Examples
  907. ========
  908. >>> from sympy import QQ
  909. >>> from sympy.polys.matrices.sdm import SDM
  910. >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0: QQ(2), 1: QQ(4)}}, (2, 2), QQ)
  911. >>> A_rref, pivots = A.rref()
  912. >>> A_null, nonpivots = A_rref.nullspace_from_rref(pivots)
  913. >>> A_null
  914. {0: {0: -2, 1: 1}}
  915. >>> pivots
  916. [0]
  917. >>> nonpivots
  918. [1]
  919. See Also
  920. ========
  921. sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace
  922. The higher-level function that would usually be called instead of
  923. calling this one directly.
  924. sympy.polys.matrices.domainmatrix.DomainMatrix.nullspace_from_rref
  925. The higher-level direct equivalent of this function.
  926. sympy.polys.matrices.ddm.DDM.nullspace_from_rref
  927. The equivalent function for dense :py:class:`~.DDM` matrices.
  928. """
  929. m, n = A.shape
  930. K = A.domain
  931. if pivots is None:
  932. pivots = sorted(map(min, A.values()))
  933. if not pivots:
  934. return A.eye((n, n), K), list(range(n))
  935. elif len(pivots) == n:
  936. return A.zeros((0, n), K), []
  937. # In fraction-free RREF the nonzero entry inserted for the pivots is
  938. # not necessarily 1.
  939. pivot_val = A[0][pivots[0]]
  940. assert not K.is_zero(pivot_val)
  941. pivots_set = set(pivots)
  942. # Loop once over all nonzero entries making a map from column indices
  943. # to the nonzero entries in that column along with the row index of the
  944. # nonzero entry. This is basically the transpose of the matrix.
  945. nonzero_cols = defaultdict(list)
  946. for i, Ai in A.items():
  947. for j, Aij in Ai.items():
  948. nonzero_cols[j].append((i, Aij))
  949. # Usually in SDM we want to avoid looping over the dimensions of the
  950. # matrix because it is optimised to support extremely sparse matrices.
  951. # Here in nullspace though every zero column becomes a nonzero column
  952. # so we need to loop once over the columns at least (range(n)) rather
  953. # than just the nonzero entries of the matrix. We can still avoid
  954. # an inner loop over the rows though by using the nonzero_cols map.
  955. basis = []
  956. nonpivots = []
  957. for j in range(n):
  958. if j in pivots_set:
  959. continue
  960. nonpivots.append(j)
  961. vec = {j: pivot_val}
  962. for ip, Aij in nonzero_cols[j]:
  963. vec[pivots[ip]] = -Aij
  964. basis.append(vec)
  965. sdm = dict(enumerate(basis))
  966. A_null = A.new(sdm, (len(basis), n), K)
  967. return (A_null, nonpivots)
  968. def particular(A):
  969. ncols = A.shape[1]
  970. B, pivots, nzcols = sdm_irref(A)
  971. P = sdm_particular_from_rref(B, ncols, pivots)
  972. rep = {0:P} if P else {}
  973. return A.new(rep, (1, ncols-1), A.domain)
  974. def hstack(A, *B):
  975. """Horizontally stacks :py:class:`~.SDM` matrices.
  976. Examples
  977. ========
  978. >>> from sympy import ZZ
  979. >>> from sympy.polys.matrices.sdm import SDM
  980. >>> A = SDM({0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}, (2, 2), ZZ)
  981. >>> B = SDM({0: {0: ZZ(5), 1: ZZ(6)}, 1: {0: ZZ(7), 1: ZZ(8)}}, (2, 2), ZZ)
  982. >>> A.hstack(B)
  983. {0: {0: 1, 1: 2, 2: 5, 3: 6}, 1: {0: 3, 1: 4, 2: 7, 3: 8}}
  984. >>> C = SDM({0: {0: ZZ(9), 1: ZZ(10)}, 1: {0: ZZ(11), 1: ZZ(12)}}, (2, 2), ZZ)
  985. >>> A.hstack(B, C)
  986. {0: {0: 1, 1: 2, 2: 5, 3: 6, 4: 9, 5: 10}, 1: {0: 3, 1: 4, 2: 7, 3: 8, 4: 11, 5: 12}}
  987. """
  988. Anew = dict(A.copy())
  989. rows, cols = A.shape
  990. domain = A.domain
  991. for Bk in B:
  992. Bkrows, Bkcols = Bk.shape
  993. assert Bkrows == rows
  994. assert Bk.domain == domain
  995. for i, Bki in Bk.items():
  996. Ai = Anew.get(i, None)
  997. if Ai is None:
  998. Anew[i] = Ai = {}
  999. for j, Bkij in Bki.items():
  1000. Ai[j + cols] = Bkij
  1001. cols += Bkcols
  1002. return A.new(Anew, (rows, cols), A.domain)
  1003. def vstack(A, *B):
  1004. """Vertically stacks :py:class:`~.SDM` matrices.
  1005. Examples
  1006. ========
  1007. >>> from sympy import ZZ
  1008. >>> from sympy.polys.matrices.sdm import SDM
  1009. >>> A = SDM({0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}, (2, 2), ZZ)
  1010. >>> B = SDM({0: {0: ZZ(5), 1: ZZ(6)}, 1: {0: ZZ(7), 1: ZZ(8)}}, (2, 2), ZZ)
  1011. >>> A.vstack(B)
  1012. {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}, 2: {0: 5, 1: 6}, 3: {0: 7, 1: 8}}
  1013. >>> C = SDM({0: {0: ZZ(9), 1: ZZ(10)}, 1: {0: ZZ(11), 1: ZZ(12)}}, (2, 2), ZZ)
  1014. >>> A.vstack(B, C)
  1015. {0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}, 2: {0: 5, 1: 6}, 3: {0: 7, 1: 8}, 4: {0: 9, 1: 10}, 5: {0: 11, 1: 12}}
  1016. """
  1017. Anew = dict(A.copy())
  1018. rows, cols = A.shape
  1019. domain = A.domain
  1020. for Bk in B:
  1021. Bkrows, Bkcols = Bk.shape
  1022. assert Bkcols == cols
  1023. assert Bk.domain == domain
  1024. for i, Bki in Bk.items():
  1025. Anew[i + rows] = Bki
  1026. rows += Bkrows
  1027. return A.new(Anew, (rows, cols), A.domain)
  1028. def applyfunc(self, func, domain):
  1029. sdm = {i: {j: func(e) for j, e in row.items()} for i, row in self.items()}
  1030. return self.new(sdm, self.shape, domain)
  1031. def charpoly(A):
  1032. """
  1033. Returns the coefficients of the characteristic polynomial
  1034. of the :py:class:`~.SDM` matrix. These elements will be domain elements.
  1035. The domain of the elements will be same as domain of the :py:class:`~.SDM`.
  1036. Examples
  1037. ========
  1038. >>> from sympy import QQ, Symbol
  1039. >>> from sympy.polys.matrices.sdm import SDM
  1040. >>> from sympy.polys import Poly
  1041. >>> A = SDM({0:{0:QQ(1), 1:QQ(2)}, 1:{0:QQ(3), 1:QQ(4)}}, (2, 2), QQ)
  1042. >>> A.charpoly()
  1043. [1, -5, -2]
  1044. We can create a polynomial using the
  1045. coefficients using :py:class:`~.Poly`
  1046. >>> x = Symbol('x')
  1047. >>> p = Poly(A.charpoly(), x, domain=A.domain)
  1048. >>> p
  1049. Poly(x**2 - 5*x - 2, x, domain='QQ')
  1050. """
  1051. K = A.domain
  1052. n, _ = A.shape
  1053. pdict = sdm_berk(A, n, K)
  1054. plist = [K.zero] * (n + 1)
  1055. for i, pi in pdict.items():
  1056. plist[i] = pi
  1057. return plist
  1058. def is_zero_matrix(self):
  1059. """
  1060. Says whether this matrix has all zero entries.
  1061. """
  1062. return not self
  1063. def is_upper(self):
  1064. """
  1065. Says whether this matrix is upper-triangular. True can be returned
  1066. even if the matrix is not square.
  1067. """
  1068. return all(i <= j for i, row in self.items() for j in row)
  1069. def is_lower(self):
  1070. """
  1071. Says whether this matrix is lower-triangular. True can be returned
  1072. even if the matrix is not square.
  1073. """
  1074. return all(i >= j for i, row in self.items() for j in row)
  1075. def is_diagonal(self):
  1076. """
  1077. Says whether this matrix is diagonal. True can be returned
  1078. even if the matrix is not square.
  1079. """
  1080. return all(i == j for i, row in self.items() for j in row)
  1081. def diagonal(self):
  1082. """
  1083. Returns the diagonal of the matrix as a list.
  1084. """
  1085. m, n = self.shape
  1086. zero = self.domain.zero
  1087. return [row.get(i, zero) for i, row in self.items() if i < n]
  1088. def lll(A, delta=QQ(3, 4)):
  1089. """
  1090. Returns the LLL-reduced basis for the :py:class:`~.SDM` matrix.
  1091. """
  1092. return A.to_dfm_or_ddm().lll(delta=delta).to_sdm()
  1093. def lll_transform(A, delta=QQ(3, 4)):
  1094. """
  1095. Returns the LLL-reduced basis and transformation matrix.
  1096. """
  1097. reduced, transform = A.to_dfm_or_ddm().lll_transform(delta=delta)
  1098. return reduced.to_sdm(), transform.to_sdm()
  1099. def binop_dict(A, B, fab, fa, fb):
  1100. Anz, Bnz = set(A), set(B)
  1101. C = {}
  1102. for i in Anz & Bnz:
  1103. Ai, Bi = A[i], B[i]
  1104. Ci = {}
  1105. Anzi, Bnzi = set(Ai), set(Bi)
  1106. for j in Anzi & Bnzi:
  1107. Cij = fab(Ai[j], Bi[j])
  1108. if Cij:
  1109. Ci[j] = Cij
  1110. for j in Anzi - Bnzi:
  1111. Cij = fa(Ai[j])
  1112. if Cij:
  1113. Ci[j] = Cij
  1114. for j in Bnzi - Anzi:
  1115. Cij = fb(Bi[j])
  1116. if Cij:
  1117. Ci[j] = Cij
  1118. if Ci:
  1119. C[i] = Ci
  1120. for i in Anz - Bnz:
  1121. Ai = A[i]
  1122. Ci = {}
  1123. for j, Aij in Ai.items():
  1124. Cij = fa(Aij)
  1125. if Cij:
  1126. Ci[j] = Cij
  1127. if Ci:
  1128. C[i] = Ci
  1129. for i in Bnz - Anz:
  1130. Bi = B[i]
  1131. Ci = {}
  1132. for j, Bij in Bi.items():
  1133. Cij = fb(Bij)
  1134. if Cij:
  1135. Ci[j] = Cij
  1136. if Ci:
  1137. C[i] = Ci
  1138. return C
  1139. def unop_dict(A, f):
  1140. B = {}
  1141. for i, Ai in A.items():
  1142. Bi = {}
  1143. for j, Aij in Ai.items():
  1144. Bij = f(Aij)
  1145. if Bij:
  1146. Bi[j] = Bij
  1147. if Bi:
  1148. B[i] = Bi
  1149. return B
  1150. def sdm_transpose(M):
  1151. MT = {}
  1152. for i, Mi in M.items():
  1153. for j, Mij in Mi.items():
  1154. try:
  1155. MT[j][i] = Mij
  1156. except KeyError:
  1157. MT[j] = {i: Mij}
  1158. return MT
  1159. def sdm_dotvec(A, B, K):
  1160. return K.sum(A[j] * B[j] for j in A.keys() & B.keys())
  1161. def sdm_matvecmul(A, B, K):
  1162. C = {}
  1163. for i, Ai in A.items():
  1164. Ci = sdm_dotvec(Ai, B, K)
  1165. if Ci:
  1166. C[i] = Ci
  1167. return C
  1168. def sdm_matmul(A, B, K, m, o):
  1169. #
  1170. # Should be fast if A and B are very sparse.
  1171. # Consider e.g. A = B = eye(1000).
  1172. #
  1173. # The idea here is that we compute C = A*B in terms of the rows of C and
  1174. # B since the dict of dicts representation naturally stores the matrix as
  1175. # rows. The ith row of C (Ci) is equal to the sum of Aik * Bk where Bk is
  1176. # the kth row of B. The algorithm below loops over each nonzero element
  1177. # Aik of A and if the corresponding row Bj is nonzero then we do
  1178. # Ci += Aik * Bk.
  1179. # To make this more efficient we don't need to loop over all elements Aik.
  1180. # Instead for each row Ai we compute the intersection of the nonzero
  1181. # columns in Ai with the nonzero rows in B. That gives the k such that
  1182. # Aik and Bk are both nonzero. In Python the intersection of two sets
  1183. # of int can be computed very efficiently.
  1184. #
  1185. if K.is_EXRAW:
  1186. return sdm_matmul_exraw(A, B, K, m, o)
  1187. C = {}
  1188. B_knz = set(B)
  1189. for i, Ai in A.items():
  1190. Ci = {}
  1191. Ai_knz = set(Ai)
  1192. for k in Ai_knz & B_knz:
  1193. Aik = Ai[k]
  1194. for j, Bkj in B[k].items():
  1195. Cij = Ci.get(j, None)
  1196. if Cij is not None:
  1197. Cij = Cij + Aik * Bkj
  1198. if Cij:
  1199. Ci[j] = Cij
  1200. else:
  1201. Ci.pop(j)
  1202. else:
  1203. Cij = Aik * Bkj
  1204. if Cij:
  1205. Ci[j] = Cij
  1206. if Ci:
  1207. C[i] = Ci
  1208. return C
  1209. def sdm_matmul_exraw(A, B, K, m, o):
  1210. #
  1211. # Like sdm_matmul above except that:
  1212. #
  1213. # - Handles cases like 0*oo -> nan (sdm_matmul skips multiplication by zero)
  1214. # - Uses K.sum (Add(*items)) for efficient addition of Expr
  1215. #
  1216. zero = K.zero
  1217. C = {}
  1218. B_knz = set(B)
  1219. for i, Ai in A.items():
  1220. Ci_list = defaultdict(list)
  1221. Ai_knz = set(Ai)
  1222. # Nonzero row/column pair
  1223. for k in Ai_knz & B_knz:
  1224. Aik = Ai[k]
  1225. if zero * Aik == zero:
  1226. # This is the main inner loop:
  1227. for j, Bkj in B[k].items():
  1228. Ci_list[j].append(Aik * Bkj)
  1229. else:
  1230. for j in range(o):
  1231. Ci_list[j].append(Aik * B[k].get(j, zero))
  1232. # Zero row in B, check for infinities in A
  1233. for k in Ai_knz - B_knz:
  1234. zAik = zero * Ai[k]
  1235. if zAik != zero:
  1236. for j in range(o):
  1237. Ci_list[j].append(zAik)
  1238. # Add terms using K.sum (Add(*terms)) for efficiency
  1239. Ci = {}
  1240. for j, Cij_list in Ci_list.items():
  1241. Cij = K.sum(Cij_list)
  1242. if Cij:
  1243. Ci[j] = Cij
  1244. if Ci:
  1245. C[i] = Ci
  1246. # Find all infinities in B
  1247. for k, Bk in B.items():
  1248. for j, Bkj in Bk.items():
  1249. if zero * Bkj != zero:
  1250. for i in range(m):
  1251. Aik = A.get(i, {}).get(k, zero)
  1252. # If Aik is not zero then this was handled above
  1253. if Aik == zero:
  1254. Ci = C.get(i, {})
  1255. Cij = Ci.get(j, zero) + Aik * Bkj
  1256. if Cij != zero:
  1257. Ci[j] = Cij
  1258. C[i] = Ci
  1259. else:
  1260. Ci.pop(j, None)
  1261. if Ci:
  1262. C[i] = Ci
  1263. else:
  1264. C.pop(i, None)
  1265. return C
  1266. def sdm_irref(A):
  1267. """RREF and pivots of a sparse matrix *A*.
  1268. Compute the reduced row echelon form (RREF) of the matrix *A* and return a
  1269. list of the pivot columns. This routine does not work in place and leaves
  1270. the original matrix *A* unmodified.
  1271. The domain of the matrix must be a field.
  1272. Examples
  1273. ========
  1274. This routine works with a dict of dicts sparse representation of a matrix:
  1275. >>> from sympy import QQ
  1276. >>> from sympy.polys.matrices.sdm import sdm_irref
  1277. >>> A = {0: {0: QQ(1), 1: QQ(2)}, 1: {0: QQ(3), 1: QQ(4)}}
  1278. >>> Arref, pivots, _ = sdm_irref(A)
  1279. >>> Arref
  1280. {0: {0: 1}, 1: {1: 1}}
  1281. >>> pivots
  1282. [0, 1]
  1283. The analogous calculation with :py:class:`~.MutableDenseMatrix` would be
  1284. >>> from sympy import Matrix
  1285. >>> M = Matrix([[1, 2], [3, 4]])
  1286. >>> Mrref, pivots = M.rref()
  1287. >>> Mrref
  1288. Matrix([
  1289. [1, 0],
  1290. [0, 1]])
  1291. >>> pivots
  1292. (0, 1)
  1293. Notes
  1294. =====
  1295. The cost of this algorithm is determined purely by the nonzero elements of
  1296. the matrix. No part of the cost of any step in this algorithm depends on
  1297. the number of rows or columns in the matrix. No step depends even on the
  1298. number of nonzero rows apart from the primary loop over those rows. The
  1299. implementation is much faster than ddm_rref for sparse matrices. In fact
  1300. at the time of writing it is also (slightly) faster than the dense
  1301. implementation even if the input is a fully dense matrix so it seems to be
  1302. faster in all cases.
  1303. The elements of the matrix should support exact division with ``/``. For
  1304. example elements of any domain that is a field (e.g. ``QQ``) should be
  1305. fine. No attempt is made to handle inexact arithmetic.
  1306. See Also
  1307. ========
  1308. sympy.polys.matrices.domainmatrix.DomainMatrix.rref
  1309. The higher-level function that would normally be used to call this
  1310. routine.
  1311. sympy.polys.matrices.dense.ddm_irref
  1312. The dense equivalent of this routine.
  1313. sdm_rref_den
  1314. Fraction-free version of this routine.
  1315. """
  1316. #
  1317. # Any zeros in the matrix are not stored at all so an element is zero if
  1318. # its row dict has no index at that key. A row is entirely zero if its
  1319. # row index is not in the outer dict. Since rref reorders the rows and
  1320. # removes zero rows we can completely discard the row indices. The first
  1321. # step then copies the row dicts into a list sorted by the index of the
  1322. # first nonzero column in each row.
  1323. #
  1324. # The algorithm then processes each row Ai one at a time. Previously seen
  1325. # rows are used to cancel their pivot columns from Ai. Then a pivot from
  1326. # Ai is chosen and is cancelled from all previously seen rows. At this
  1327. # point Ai joins the previously seen rows. Once all rows are seen all
  1328. # elimination has occurred and the rows are sorted by pivot column index.
  1329. #
  1330. # The previously seen rows are stored in two separate groups. The reduced
  1331. # group consists of all rows that have been reduced to a single nonzero
  1332. # element (the pivot). There is no need to attempt any further reduction
  1333. # with these. Rows that still have other nonzeros need to be considered
  1334. # when Ai is cancelled from the previously seen rows.
  1335. #
  1336. # A dict nonzerocolumns is used to map from a column index to a set of
  1337. # previously seen rows that still have a nonzero element in that column.
  1338. # This means that we can cancel the pivot from Ai into the previously seen
  1339. # rows without needing to loop over each row that might have a zero in
  1340. # that column.
  1341. #
  1342. # Row dicts sorted by index of first nonzero column
  1343. # (Maybe sorting is not needed/useful.)
  1344. Arows = sorted((Ai.copy() for Ai in A.values()), key=min)
  1345. # Each processed row has an associated pivot column.
  1346. # pivot_row_map maps from the pivot column index to the row dict.
  1347. # This means that we can represent a set of rows purely as a set of their
  1348. # pivot indices.
  1349. pivot_row_map = {}
  1350. # Set of pivot indices for rows that are fully reduced to a single nonzero.
  1351. reduced_pivots = set()
  1352. # Set of pivot indices for rows not fully reduced
  1353. nonreduced_pivots = set()
  1354. # Map from column index to a set of pivot indices representing the rows
  1355. # that have a nonzero at that column.
  1356. nonzero_columns = defaultdict(set)
  1357. while Arows:
  1358. # Select pivot element and row
  1359. Ai = Arows.pop()
  1360. # Nonzero columns from fully reduced pivot rows can be removed
  1361. Ai = {j: Aij for j, Aij in Ai.items() if j not in reduced_pivots}
  1362. # Others require full row cancellation
  1363. for j in nonreduced_pivots & set(Ai):
  1364. Aj = pivot_row_map[j]
  1365. Aij = Ai[j]
  1366. Ainz = set(Ai)
  1367. Ajnz = set(Aj)
  1368. for k in Ajnz - Ainz:
  1369. Ai[k] = - Aij * Aj[k]
  1370. Ai.pop(j)
  1371. Ainz.remove(j)
  1372. for k in Ajnz & Ainz:
  1373. Aik = Ai[k] - Aij * Aj[k]
  1374. if Aik:
  1375. Ai[k] = Aik
  1376. else:
  1377. Ai.pop(k)
  1378. # We have now cancelled previously seen pivots from Ai.
  1379. # If it is zero then discard it.
  1380. if not Ai:
  1381. continue
  1382. # Choose a pivot from Ai:
  1383. j = min(Ai)
  1384. Aij = Ai[j]
  1385. pivot_row_map[j] = Ai
  1386. Ainz = set(Ai)
  1387. # Normalise the pivot row to make the pivot 1.
  1388. #
  1389. # This approach is slow for some domains. Cross cancellation might be
  1390. # better for e.g. QQ(x) with division delayed to the final steps.
  1391. Aijinv = Aij**-1
  1392. for l in Ai:
  1393. Ai[l] *= Aijinv
  1394. # Use Aij to cancel column j from all previously seen rows
  1395. for k in nonzero_columns.pop(j, ()):
  1396. Ak = pivot_row_map[k]
  1397. Akj = Ak[j]
  1398. Aknz = set(Ak)
  1399. for l in Ainz - Aknz:
  1400. Ak[l] = - Akj * Ai[l]
  1401. nonzero_columns[l].add(k)
  1402. Ak.pop(j)
  1403. Aknz.remove(j)
  1404. for l in Ainz & Aknz:
  1405. Akl = Ak[l] - Akj * Ai[l]
  1406. if Akl:
  1407. Ak[l] = Akl
  1408. else:
  1409. # Drop nonzero elements
  1410. Ak.pop(l)
  1411. if l != j:
  1412. nonzero_columns[l].remove(k)
  1413. if len(Ak) == 1:
  1414. reduced_pivots.add(k)
  1415. nonreduced_pivots.remove(k)
  1416. if len(Ai) == 1:
  1417. reduced_pivots.add(j)
  1418. else:
  1419. nonreduced_pivots.add(j)
  1420. for l in Ai:
  1421. if l != j:
  1422. nonzero_columns[l].add(j)
  1423. # All done!
  1424. pivots = sorted(reduced_pivots | nonreduced_pivots)
  1425. pivot2row = {p: n for n, p in enumerate(pivots)}
  1426. nonzero_columns = {c: {pivot2row[p] for p in s} for c, s in nonzero_columns.items()}
  1427. rows = [pivot_row_map[i] for i in pivots]
  1428. rref = dict(enumerate(rows))
  1429. return rref, pivots, nonzero_columns
  1430. def sdm_rref_den(A, K):
  1431. """
  1432. Return the reduced row echelon form (RREF) of A with denominator.
  1433. The RREF is computed using fraction-free Gauss-Jordan elimination.
  1434. Explanation
  1435. ===========
  1436. The algorithm used is the fraction-free version of Gauss-Jordan elimination
  1437. described as FFGJ in [1]_. Here it is modified to handle zero or missing
  1438. pivots and to avoid redundant arithmetic. This implementation is also
  1439. optimized for sparse matrices.
  1440. The domain $K$ must support exact division (``K.exquo``) but does not need
  1441. to be a field. This method is suitable for most exact rings and fields like
  1442. :ref:`ZZ`, :ref:`QQ` and :ref:`QQ(a)`. In the case of :ref:`QQ` or
  1443. :ref:`K(x)` it might be more efficient to clear denominators and use
  1444. :ref:`ZZ` or :ref:`K[x]` instead.
  1445. For inexact domains like :ref:`RR` and :ref:`CC` use ``ddm_irref`` instead.
  1446. Examples
  1447. ========
  1448. >>> from sympy.polys.matrices.sdm import sdm_rref_den
  1449. >>> from sympy.polys.domains import ZZ
  1450. >>> A = {0: {0: ZZ(1), 1: ZZ(2)}, 1: {0: ZZ(3), 1: ZZ(4)}}
  1451. >>> A_rref, den, pivots = sdm_rref_den(A, ZZ)
  1452. >>> A_rref
  1453. {0: {0: -2}, 1: {1: -2}}
  1454. >>> den
  1455. -2
  1456. >>> pivots
  1457. [0, 1]
  1458. See Also
  1459. ========
  1460. sympy.polys.matrices.domainmatrix.DomainMatrix.rref_den
  1461. Higher-level interface to ``sdm_rref_den`` that would usually be used
  1462. instead of calling this function directly.
  1463. sympy.polys.matrices.sdm.sdm_rref_den
  1464. The ``SDM`` method that uses this function.
  1465. sdm_irref
  1466. Computes RREF using field division.
  1467. ddm_irref_den
  1468. The dense version of this algorithm.
  1469. References
  1470. ==========
  1471. .. [1] Fraction-free algorithms for linear and polynomial equations.
  1472. George C. Nakos , Peter R. Turner , Robert M. Williams.
  1473. https://dl.acm.org/doi/10.1145/271130.271133
  1474. """
  1475. #
  1476. # We represent each row of the matrix as a dict mapping column indices to
  1477. # nonzero elements. We will build the RREF matrix starting from an empty
  1478. # matrix and appending one row at a time. At each step we will have the
  1479. # RREF of the rows we have processed so far.
  1480. #
  1481. # Our representation of the RREF divides it into three parts:
  1482. #
  1483. # 1. Fully reduced rows having only a single nonzero element (the pivot).
  1484. # 2. Partially reduced rows having nonzeros after the pivot.
  1485. # 3. The current denominator and divisor.
  1486. #
  1487. # For example if the incremental RREF might be:
  1488. #
  1489. # [2, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  1490. # [0, 0, 2, 0, 0, 0, 7, 0, 0, 0]
  1491. # [0, 0, 0, 0, 0, 2, 0, 0, 0, 0]
  1492. # [0, 0, 0, 0, 0, 0, 0, 2, 0, 0]
  1493. # [0, 0, 0, 0, 0, 0, 0, 0, 2, 0]
  1494. #
  1495. # Here the second row is partially reduced and the other rows are fully
  1496. # reduced. The denominator would be 2 in this case. We distinguish the
  1497. # fully reduced rows because we can handle them more efficiently when
  1498. # adding a new row.
  1499. #
  1500. # When adding a new row we need to multiply it by the current denominator.
  1501. # Then we reduce the new row by cross cancellation with the previous rows.
  1502. # Then if it is not reduced to zero we take its leading entry as the new
  1503. # pivot, cross cancel the new row from the previous rows and update the
  1504. # denominator. In the fraction-free version this last step requires
  1505. # multiplying and dividing the whole matrix by the new pivot and the
  1506. # current divisor. The advantage of building the RREF one row at a time is
  1507. # that in the sparse case we only need to work with the relatively sparse
  1508. # upper rows of the matrix. The simple version of FFGJ in [1] would
  1509. # multiply and divide all the dense lower rows at each step.
  1510. # Handle the trivial cases.
  1511. if not A:
  1512. return ({}, K.one, [])
  1513. elif len(A) == 1:
  1514. Ai, = A.values()
  1515. j = min(Ai)
  1516. Aij = Ai[j]
  1517. return ({0: Ai.copy()}, Aij, [j])
  1518. # For inexact domains like RR[x] we use quo and discard the remainder.
  1519. # Maybe it would be better for K.exquo to do this automatically.
  1520. if K.is_Exact:
  1521. exquo = K.exquo
  1522. else:
  1523. exquo = K.quo
  1524. # Make sure we have the rows in order to make this deterministic from the
  1525. # outset.
  1526. _, rows_in_order = zip(*sorted(A.items()))
  1527. col_to_row_reduced = {}
  1528. col_to_row_unreduced = {}
  1529. reduced = col_to_row_reduced.keys()
  1530. unreduced = col_to_row_unreduced.keys()
  1531. # Our representation of the RREF so far.
  1532. A_rref_rows = []
  1533. denom = None
  1534. divisor = None
  1535. # The rows that remain to be added to the RREF. These are sorted by the
  1536. # column index of their leading entry. Note that sorted() is stable so the
  1537. # previous sort by unique row index is still needed to make this
  1538. # deterministic (there may be multiple rows with the same leading column).
  1539. A_rows = sorted(rows_in_order, key=min)
  1540. for Ai in A_rows:
  1541. # All fully reduced columns can be immediately discarded.
  1542. Ai = {j: Aij for j, Aij in Ai.items() if j not in reduced}
  1543. # We need to multiply the new row by the current denominator to bring
  1544. # it into the same scale as the previous rows and then cross-cancel to
  1545. # reduce it wrt the previous unreduced rows. All pivots in the previous
  1546. # rows are equal to denom so the coefficients we need to make a linear
  1547. # combination of the previous rows to cancel into the new row are just
  1548. # the ones that are already in the new row *before* we multiply by
  1549. # denom. We compute that linear combination first and then multiply the
  1550. # new row by denom before subtraction.
  1551. Ai_cancel = {}
  1552. for j in unreduced & Ai.keys():
  1553. # Remove the pivot column from the new row since it would become
  1554. # zero anyway.
  1555. Aij = Ai.pop(j)
  1556. Aj = A_rref_rows[col_to_row_unreduced[j]]
  1557. for k, Ajk in Aj.items():
  1558. Aik_cancel = Ai_cancel.get(k)
  1559. if Aik_cancel is None:
  1560. Ai_cancel[k] = Aij * Ajk
  1561. else:
  1562. Aik_cancel = Aik_cancel + Aij * Ajk
  1563. if Aik_cancel:
  1564. Ai_cancel[k] = Aik_cancel
  1565. else:
  1566. Ai_cancel.pop(k)
  1567. # Multiply the new row by the current denominator and subtract.
  1568. Ai_nz = set(Ai)
  1569. Ai_cancel_nz = set(Ai_cancel)
  1570. d = denom or K.one
  1571. for k in Ai_cancel_nz - Ai_nz:
  1572. Ai[k] = -Ai_cancel[k]
  1573. for k in Ai_nz - Ai_cancel_nz:
  1574. Ai[k] = Ai[k] * d
  1575. for k in Ai_cancel_nz & Ai_nz:
  1576. Aik = Ai[k] * d - Ai_cancel[k]
  1577. if Aik:
  1578. Ai[k] = Aik
  1579. else:
  1580. Ai.pop(k)
  1581. # Now Ai has the same scale as the other rows and is reduced wrt the
  1582. # unreduced rows.
  1583. # If the row is reduced to zero then discard it.
  1584. if not Ai:
  1585. continue
  1586. # Choose a pivot for this row.
  1587. j = min(Ai)
  1588. Aij = Ai.pop(j)
  1589. # Cross cancel the unreduced rows by the new row.
  1590. # a[k][l] = (a[i][j]*a[k][l] - a[k][j]*a[i][l]) / divisor
  1591. for pk, k in list(col_to_row_unreduced.items()):
  1592. Ak = A_rref_rows[k]
  1593. if j not in Ak:
  1594. # This row is already reduced wrt the new row but we need to
  1595. # bring it to the same scale as the new denominator. This step
  1596. # is not needed in sdm_irref.
  1597. for l, Akl in Ak.items():
  1598. Akl = Akl * Aij
  1599. if divisor is not None:
  1600. Akl = exquo(Akl, divisor)
  1601. Ak[l] = Akl
  1602. continue
  1603. Akj = Ak.pop(j)
  1604. Ai_nz = set(Ai)
  1605. Ak_nz = set(Ak)
  1606. for l in Ai_nz - Ak_nz:
  1607. Ak[l] = - Akj * Ai[l]
  1608. if divisor is not None:
  1609. Ak[l] = exquo(Ak[l], divisor)
  1610. # This loop also not needed in sdm_irref.
  1611. for l in Ak_nz - Ai_nz:
  1612. Ak[l] = Aij * Ak[l]
  1613. if divisor is not None:
  1614. Ak[l] = exquo(Ak[l], divisor)
  1615. for l in Ai_nz & Ak_nz:
  1616. Akl = Aij * Ak[l] - Akj * Ai[l]
  1617. if Akl:
  1618. if divisor is not None:
  1619. Akl = exquo(Akl, divisor)
  1620. Ak[l] = Akl
  1621. else:
  1622. Ak.pop(l)
  1623. if not Ak:
  1624. col_to_row_unreduced.pop(pk)
  1625. col_to_row_reduced[pk] = k
  1626. i = len(A_rref_rows)
  1627. A_rref_rows.append(Ai)
  1628. if Ai:
  1629. col_to_row_unreduced[j] = i
  1630. else:
  1631. col_to_row_reduced[j] = i
  1632. # Update the denominator.
  1633. if not K.is_one(Aij):
  1634. if denom is None:
  1635. denom = Aij
  1636. else:
  1637. denom *= Aij
  1638. if divisor is not None:
  1639. denom = exquo(denom, divisor)
  1640. # Update the divisor.
  1641. divisor = denom
  1642. if denom is None:
  1643. denom = K.one
  1644. # Sort the rows by their leading column index.
  1645. col_to_row = {**col_to_row_reduced, **col_to_row_unreduced}
  1646. row_to_col = {i: j for j, i in col_to_row.items()}
  1647. A_rref_rows_col = [(row_to_col[i], Ai) for i, Ai in enumerate(A_rref_rows)]
  1648. pivots, A_rref = zip(*sorted(A_rref_rows_col))
  1649. pivots = list(pivots)
  1650. # Insert the pivot values
  1651. for i, Ai in enumerate(A_rref):
  1652. Ai[pivots[i]] = denom
  1653. A_rref_sdm = dict(enumerate(A_rref))
  1654. return A_rref_sdm, denom, pivots
  1655. def sdm_nullspace_from_rref(A, one, ncols, pivots, nonzero_cols):
  1656. """Get nullspace from A which is in RREF"""
  1657. nonpivots = sorted(set(range(ncols)) - set(pivots))
  1658. K = []
  1659. for j in nonpivots:
  1660. Kj = {j:one}
  1661. for i in nonzero_cols.get(j, ()):
  1662. Kj[pivots[i]] = -A[i][j]
  1663. K.append(Kj)
  1664. return K, nonpivots
  1665. def sdm_particular_from_rref(A, ncols, pivots):
  1666. """Get a particular solution from A which is in RREF"""
  1667. P = {}
  1668. for i, j in enumerate(pivots):
  1669. Ain = A[i].get(ncols-1, None)
  1670. if Ain is not None:
  1671. P[j] = Ain / A[i][j]
  1672. return P
  1673. def sdm_berk(M, n, K):
  1674. """
  1675. Berkowitz algorithm for computing the characteristic polynomial.
  1676. Explanation
  1677. ===========
  1678. The Berkowitz algorithm is a division-free algorithm for computing the
  1679. characteristic polynomial of a matrix over any commutative ring using only
  1680. arithmetic in the coefficient ring. This implementation is for sparse
  1681. matrices represented in a dict-of-dicts format (like :class:`SDM`).
  1682. Examples
  1683. ========
  1684. >>> from sympy import Matrix
  1685. >>> from sympy.polys.matrices.sdm import sdm_berk
  1686. >>> from sympy.polys.domains import ZZ
  1687. >>> M = {0: {0: ZZ(1), 1:ZZ(2)}, 1: {0:ZZ(3), 1:ZZ(4)}}
  1688. >>> sdm_berk(M, 2, ZZ)
  1689. {0: 1, 1: -5, 2: -2}
  1690. >>> Matrix([[1, 2], [3, 4]]).charpoly()
  1691. PurePoly(lambda**2 - 5*lambda - 2, lambda, domain='ZZ')
  1692. See Also
  1693. ========
  1694. sympy.polys.matrices.domainmatrix.DomainMatrix.charpoly
  1695. The high-level interface to this function.
  1696. sympy.polys.matrices.dense.ddm_berk
  1697. The dense version of this function.
  1698. References
  1699. ==========
  1700. .. [1] https://en.wikipedia.org/wiki/Samuelson%E2%80%93Berkowitz_algorithm
  1701. """
  1702. zero = K.zero
  1703. one = K.one
  1704. if n == 0:
  1705. return {0: one}
  1706. elif n == 1:
  1707. pdict = {0: one}
  1708. if M00 := M.get(0, {}).get(0, zero):
  1709. pdict[1] = -M00
  1710. # M = [[a, R],
  1711. # [C, A]]
  1712. a, R, C, A = K.zero, {}, {}, defaultdict(dict)
  1713. for i, Mi in M.items():
  1714. for j, Mij in Mi.items():
  1715. if i and j:
  1716. A[i-1][j-1] = Mij
  1717. elif i:
  1718. C[i-1] = Mij
  1719. elif j:
  1720. R[j-1] = Mij
  1721. else:
  1722. a = Mij
  1723. # T = [ 1, 0, 0, 0, 0, ... ]
  1724. # [ -a, 1, 0, 0, 0, ... ]
  1725. # [ -R*C, -a, 1, 0, 0, ... ]
  1726. # [ -R*A*C, -R*C, -a, 1, 0, ... ]
  1727. # [-R*A^2*C, -R*A*C, -R*C, -a, 1, ... ]
  1728. # [ ... ]
  1729. # T is (n+1) x n
  1730. #
  1731. # In the sparse case we might have A^m*C = 0 for some m making T banded
  1732. # rather than triangular so we just compute the nonzero entries of the
  1733. # first column rather than constructing the matrix explicitly.
  1734. AnC = C
  1735. RC = sdm_dotvec(R, C, K)
  1736. Tvals = [one, -a, -RC]
  1737. for i in range(3, n+1):
  1738. AnC = sdm_matvecmul(A, AnC, K)
  1739. if not AnC:
  1740. break
  1741. RAnC = sdm_dotvec(R, AnC, K)
  1742. Tvals.append(-RAnC)
  1743. # Strip trailing zeros
  1744. while Tvals and not Tvals[-1]:
  1745. Tvals.pop()
  1746. q = sdm_berk(A, n-1, K)
  1747. # This would be the explicit multiplication T*q but we can do better:
  1748. #
  1749. # T = {}
  1750. # for i in range(n+1):
  1751. # Ti = {}
  1752. # for j in range(max(0, i-len(Tvals)+1), min(i+1, n)):
  1753. # Ti[j] = Tvals[i-j]
  1754. # T[i] = Ti
  1755. # Tq = sdm_matvecmul(T, q, K)
  1756. #
  1757. # In the sparse case q might be mostly zero. We know that T[i,j] is nonzero
  1758. # for i <= j < i + len(Tvals) so if q does not have a nonzero entry in that
  1759. # range then Tq[j] must be zero. We exploit this potential banded
  1760. # structure and the potential sparsity of q to compute Tq more efficiently.
  1761. Tvals = Tvals[::-1]
  1762. Tq = {}
  1763. for i in range(min(q), min(max(q)+len(Tvals), n+1)):
  1764. Ti = dict(enumerate(Tvals, i-len(Tvals)+1))
  1765. if Tqi := sdm_dotvec(Ti, q, K):
  1766. Tq[i] = Tqi
  1767. return Tq