| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983 |
- """
- Module for the DomainMatrix class.
- A DomainMatrix represents a matrix with elements that are in a particular
- Domain. Each DomainMatrix internally wraps a DDM which is used for the
- lower-level operations. The idea is that the DomainMatrix class provides the
- convenience routines for converting between Expr and the poly domains as well
- as unifying matrices with different domains.
- """
- from __future__ import annotations
- from collections import Counter
- from functools import reduce
- from sympy.external.gmpy import GROUND_TYPES
- from sympy.utilities.decorator import doctest_depends_on
- from sympy.core.sympify import _sympify
- from ..domains import Domain
- from ..constructor import construct_domain
- from .exceptions import (
- DMFormatError,
- DMBadInputError,
- DMShapeError,
- DMDomainError,
- DMNotAField,
- DMNonSquareMatrixError,
- DMNonInvertibleMatrixError
- )
- from .domainscalar import DomainScalar
- from sympy.polys.domains import ZZ, EXRAW, QQ
- from sympy.polys.densearith import dup_mul
- from sympy.polys.densebasic import dup_convert
- from sympy.polys.densetools import (
- dup_mul_ground,
- dup_quo_ground,
- dup_content,
- dup_clear_denoms,
- dup_primitive,
- dup_transform,
- )
- from sympy.polys.factortools import dup_factor_list
- from sympy.polys.polyutils import _sort_factors
- from .ddm import DDM
- from .sdm import SDM
- from .dfm import DFM
- from .rref import _dm_rref, _dm_rref_den
- if GROUND_TYPES != 'flint':
- __doctest_skip__ = ['DomainMatrix.to_dfm', 'DomainMatrix.to_dfm_or_ddm']
- else:
- __doctest_skip__ = ['DomainMatrix.from_list']
- def DM(rows, domain):
- """Convenient alias for DomainMatrix.from_list
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> DM([[1, 2], [3, 4]], ZZ)
- DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
- See Also
- ========
- DomainMatrix.from_list
- """
- return DomainMatrix.from_list(rows, domain)
- class DomainMatrix:
- r"""
- Associate Matrix with :py:class:`~.Domain`
- Explanation
- ===========
- DomainMatrix uses :py:class:`~.Domain` for its internal representation
- which makes it faster than the SymPy Matrix class (currently) for many
- common operations, but this advantage makes it not entirely compatible
- with Matrix. DomainMatrix are analogous to numpy arrays with "dtype".
- In the DomainMatrix, each element has a domain such as :ref:`ZZ`
- or :ref:`QQ(a)`.
- Examples
- ========
- Creating a DomainMatrix from the existing Matrix class:
- >>> from sympy import Matrix
- >>> from sympy.polys.matrices import DomainMatrix
- >>> Matrix1 = Matrix([
- ... [1, 2],
- ... [3, 4]])
- >>> A = DomainMatrix.from_Matrix(Matrix1)
- >>> A
- DomainMatrix({0: {0: 1, 1: 2}, 1: {0: 3, 1: 4}}, (2, 2), ZZ)
- Directly forming a DomainMatrix:
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> A
- DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
- See Also
- ========
- DDM
- SDM
- Domain
- Poly
- """
- rep: SDM | DDM | DFM
- shape: tuple[int, int]
- domain: Domain
- def __new__(cls, rows, shape, domain, *, fmt=None):
- """
- Creates a :py:class:`~.DomainMatrix`.
- Parameters
- ==========
- rows : Represents elements of DomainMatrix as list of lists
- shape : Represents dimension of DomainMatrix
- domain : Represents :py:class:`~.Domain` of DomainMatrix
- Raises
- ======
- TypeError
- If any of rows, shape and domain are not provided
- """
- if isinstance(rows, (DDM, SDM, DFM)):
- raise TypeError("Use from_rep to initialise from SDM/DDM")
- elif isinstance(rows, list):
- rep = DDM(rows, shape, domain)
- elif isinstance(rows, dict):
- rep = SDM(rows, shape, domain)
- else:
- msg = "Input should be list-of-lists or dict-of-dicts"
- raise TypeError(msg)
- if fmt is not None:
- if fmt == 'sparse':
- rep = rep.to_sdm()
- elif fmt == 'dense':
- rep = rep.to_ddm()
- else:
- raise ValueError("fmt should be 'sparse' or 'dense'")
- # Use python-flint for dense matrices if possible
- if rep.fmt == 'dense' and DFM._supports_domain(domain):
- rep = rep.to_dfm()
- return cls.from_rep(rep)
- def __reduce__(self):
- rep = self.rep
- if rep.fmt == 'dense':
- arg = self.to_list()
- elif rep.fmt == 'sparse':
- arg = dict(rep)
- else:
- raise RuntimeError # pragma: no cover
- args = (arg, rep.shape, rep.domain)
- return (self.__class__, args)
- def __getitem__(self, key):
- i, j = key
- m, n = self.shape
- if not (isinstance(i, slice) or isinstance(j, slice)):
- return DomainScalar(self.rep.getitem(i, j), self.domain)
- if not isinstance(i, slice):
- if not -m <= i < m:
- raise IndexError("Row index out of range")
- i = i % m
- i = slice(i, i+1)
- if not isinstance(j, slice):
- if not -n <= j < n:
- raise IndexError("Column index out of range")
- j = j % n
- j = slice(j, j+1)
- return self.from_rep(self.rep.extract_slice(i, j))
- def getitem_sympy(self, i, j):
- return self.domain.to_sympy(self.rep.getitem(i, j))
- def extract(self, rowslist, colslist):
- return self.from_rep(self.rep.extract(rowslist, colslist))
- def __setitem__(self, key, value):
- i, j = key
- if not self.domain.of_type(value):
- raise TypeError
- if isinstance(i, int) and isinstance(j, int):
- self.rep.setitem(i, j, value)
- else:
- raise NotImplementedError
- @classmethod
- def from_rep(cls, rep):
- """Create a new DomainMatrix efficiently from DDM/SDM.
- Examples
- ========
- Create a :py:class:`~.DomainMatrix` with an dense internal
- representation as :py:class:`~.DDM`:
- >>> from sympy.polys.domains import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy.polys.matrices.ddm import DDM
- >>> drep = DDM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> dM = DomainMatrix.from_rep(drep)
- >>> dM
- DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
- Create a :py:class:`~.DomainMatrix` with a sparse internal
- representation as :py:class:`~.SDM`:
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy.polys.matrices.sdm import SDM
- >>> from sympy import ZZ
- >>> drep = SDM({0:{1:ZZ(1)},1:{0:ZZ(2)}}, (2, 2), ZZ)
- >>> dM = DomainMatrix.from_rep(drep)
- >>> dM
- DomainMatrix({0: {1: 1}, 1: {0: 2}}, (2, 2), ZZ)
- Parameters
- ==========
- rep: SDM or DDM
- The internal sparse or dense representation of the matrix.
- Returns
- =======
- DomainMatrix
- A :py:class:`~.DomainMatrix` wrapping *rep*.
- Notes
- =====
- This takes ownership of rep as its internal representation. If rep is
- being mutated elsewhere then a copy should be provided to
- ``from_rep``. Only minimal verification or checking is done on *rep*
- as this is supposed to be an efficient internal routine.
- """
- if not (isinstance(rep, (DDM, SDM)) or (DFM is not None and isinstance(rep, DFM))):
- raise TypeError("rep should be of type DDM or SDM")
- self = super().__new__(cls)
- self.rep = rep
- self.shape = rep.shape
- self.domain = rep.domain
- return self
- @classmethod
- @doctest_depends_on(ground_types=['python', 'gmpy'])
- def from_list(cls, rows, domain):
- r"""
- Convert a list of lists into a DomainMatrix
- Parameters
- ==========
- rows: list of lists
- Each element of the inner lists should be either the single arg,
- or tuple of args, that would be passed to the domain constructor
- in order to form an element of the domain. See examples.
- Returns
- =======
- DomainMatrix containing elements defined in rows
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import FF, QQ, ZZ
- >>> A = DomainMatrix.from_list([[1, 0, 1], [0, 0, 1]], ZZ)
- >>> A
- DomainMatrix([[1, 0, 1], [0, 0, 1]], (2, 3), ZZ)
- >>> B = DomainMatrix.from_list([[1, 0, 1], [0, 0, 1]], FF(7))
- >>> B
- DomainMatrix([[1 mod 7, 0 mod 7, 1 mod 7], [0 mod 7, 0 mod 7, 1 mod 7]], (2, 3), GF(7))
- >>> C = DomainMatrix.from_list([[(1, 2), (3, 1)], [(1, 4), (5, 1)]], QQ)
- >>> C
- DomainMatrix([[1/2, 3], [1/4, 5]], (2, 2), QQ)
- See Also
- ========
- from_list_sympy
- """
- nrows = len(rows)
- ncols = 0 if not nrows else len(rows[0])
- conv = lambda e: domain(*e) if isinstance(e, tuple) else domain(e)
- domain_rows = [[conv(e) for e in row] for row in rows]
- return DomainMatrix(domain_rows, (nrows, ncols), domain)
- @classmethod
- def from_list_sympy(cls, nrows, ncols, rows, **kwargs):
- r"""
- Convert a list of lists of Expr into a DomainMatrix using construct_domain
- Parameters
- ==========
- nrows: number of rows
- ncols: number of columns
- rows: list of lists
- Returns
- =======
- DomainMatrix containing elements of rows
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy.abc import x, y, z
- >>> A = DomainMatrix.from_list_sympy(1, 3, [[x, y, z]])
- >>> A
- DomainMatrix([[x, y, z]], (1, 3), ZZ[x,y,z])
- See Also
- ========
- sympy.polys.constructor.construct_domain, from_dict_sympy
- """
- assert len(rows) == nrows
- assert all(len(row) == ncols for row in rows)
- items_sympy = [_sympify(item) for row in rows for item in row]
- domain, items_domain = cls.get_domain(items_sympy, **kwargs)
- domain_rows = [[items_domain[ncols*r + c] for c in range(ncols)] for r in range(nrows)]
- return DomainMatrix(domain_rows, (nrows, ncols), domain)
- @classmethod
- def from_dict_sympy(cls, nrows, ncols, elemsdict, **kwargs):
- """
- Parameters
- ==========
- nrows: number of rows
- ncols: number of cols
- elemsdict: dict of dicts containing non-zero elements of the DomainMatrix
- Returns
- =======
- DomainMatrix containing elements of elemsdict
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy.abc import x,y,z
- >>> elemsdict = {0: {0:x}, 1:{1: y}, 2: {2: z}}
- >>> A = DomainMatrix.from_dict_sympy(3, 3, elemsdict)
- >>> A
- DomainMatrix({0: {0: x}, 1: {1: y}, 2: {2: z}}, (3, 3), ZZ[x,y,z])
- See Also
- ========
- from_list_sympy
- """
- if not all(0 <= r < nrows for r in elemsdict):
- raise DMBadInputError("Row out of range")
- if not all(0 <= c < ncols for row in elemsdict.values() for c in row):
- raise DMBadInputError("Column out of range")
- items_sympy = [_sympify(item) for row in elemsdict.values() for item in row.values()]
- domain, items_domain = cls.get_domain(items_sympy, **kwargs)
- idx = 0
- items_dict = {}
- for i, row in elemsdict.items():
- items_dict[i] = {}
- for j in row:
- items_dict[i][j] = items_domain[idx]
- idx += 1
- return DomainMatrix(items_dict, (nrows, ncols), domain)
- @classmethod
- def from_Matrix(cls, M, fmt='sparse',**kwargs):
- r"""
- Convert Matrix to DomainMatrix
- Parameters
- ==========
- M: Matrix
- Returns
- =======
- Returns DomainMatrix with identical elements as M
- Examples
- ========
- >>> from sympy import Matrix
- >>> from sympy.polys.matrices import DomainMatrix
- >>> M = Matrix([
- ... [1.0, 3.4],
- ... [2.4, 1]])
- >>> A = DomainMatrix.from_Matrix(M)
- >>> A
- DomainMatrix({0: {0: 1.0, 1: 3.4}, 1: {0: 2.4, 1: 1.0}}, (2, 2), RR)
- We can keep internal representation as ddm using fmt='dense'
- >>> from sympy import Matrix, QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix.from_Matrix(Matrix([[QQ(1, 2), QQ(3, 4)], [QQ(0, 1), QQ(0, 1)]]), fmt='dense')
- >>> A.rep
- [[1/2, 3/4], [0, 0]]
- See Also
- ========
- Matrix
- """
- if fmt == 'dense':
- return cls.from_list_sympy(*M.shape, M.tolist(), **kwargs)
- return cls.from_dict_sympy(*M.shape, M.todod(), **kwargs)
- @classmethod
- def get_domain(cls, items_sympy, **kwargs):
- K, items_K = construct_domain(items_sympy, **kwargs)
- return K, items_K
- def choose_domain(self, **opts):
- """Convert to a domain found by :func:`~.construct_domain`.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> M = DM([[1, 2], [3, 4]], ZZ)
- >>> M
- DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
- >>> M.choose_domain(field=True)
- DomainMatrix([[1, 2], [3, 4]], (2, 2), QQ)
- >>> from sympy.abc import x
- >>> M = DM([[1, x], [x**2, x**3]], ZZ[x])
- >>> M.choose_domain(field=True).domain
- ZZ(x)
- Keyword arguments are passed to :func:`~.construct_domain`.
- See Also
- ========
- construct_domain
- convert_to
- """
- elements, data = self.to_sympy().to_flat_nz()
- dom, elements_dom = construct_domain(elements, **opts)
- return self.from_flat_nz(elements_dom, data, dom)
- def copy(self):
- return self.from_rep(self.rep.copy())
- def convert_to(self, K):
- r"""
- Change the domain of DomainMatrix to desired domain or field
- Parameters
- ==========
- K : Represents the desired domain or field.
- Alternatively, ``None`` may be passed, in which case this method
- just returns a copy of this DomainMatrix.
- Returns
- =======
- DomainMatrix
- DomainMatrix with the desired domain or field
- Examples
- ========
- >>> from sympy import ZZ, ZZ_I
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> A.convert_to(ZZ_I)
- DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ_I)
- """
- if K == self.domain:
- return self.copy()
- rep = self.rep
- # The DFM, DDM and SDM types do not do any implicit conversions so we
- # manage switching between DDM and DFM here.
- if rep.is_DFM and not DFM._supports_domain(K):
- rep_K = rep.to_ddm().convert_to(K)
- elif rep.is_DDM and DFM._supports_domain(K):
- rep_K = rep.convert_to(K).to_dfm()
- else:
- rep_K = rep.convert_to(K)
- return self.from_rep(rep_K)
- def to_sympy(self):
- return self.convert_to(EXRAW)
- def to_field(self):
- r"""
- Returns a DomainMatrix with the appropriate field
- Returns
- =======
- DomainMatrix
- DomainMatrix with the appropriate field
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> A.to_field()
- DomainMatrix([[1, 2], [3, 4]], (2, 2), QQ)
- """
- K = self.domain.get_field()
- return self.convert_to(K)
- def to_sparse(self):
- """
- Return a sparse DomainMatrix representation of *self*.
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import QQ
- >>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ)
- >>> A.rep
- [[1, 0], [0, 2]]
- >>> B = A.to_sparse()
- >>> B.rep
- {0: {0: 1}, 1: {1: 2}}
- """
- if self.rep.fmt == 'sparse':
- return self
- return self.from_rep(self.rep.to_sdm())
- def to_dense(self):
- """
- Return a dense DomainMatrix representation of *self*.
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import QQ
- >>> A = DomainMatrix({0: {0: 1}, 1: {1: 2}}, (2, 2), QQ)
- >>> A.rep
- {0: {0: 1}, 1: {1: 2}}
- >>> B = A.to_dense()
- >>> B.rep
- [[1, 0], [0, 2]]
- """
- rep = self.rep
- if rep.fmt == 'dense':
- return self
- return self.from_rep(rep.to_dfm_or_ddm())
- def to_ddm(self):
- """
- Return a :class:`~.DDM` representation of *self*.
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import QQ
- >>> A = DomainMatrix({0: {0: 1}, 1: {1: 2}}, (2, 2), QQ)
- >>> ddm = A.to_ddm()
- >>> ddm
- [[1, 0], [0, 2]]
- >>> type(ddm)
- <class 'sympy.polys.matrices.ddm.DDM'>
- See Also
- ========
- to_sdm
- to_dense
- sympy.polys.matrices.ddm.DDM.to_sdm
- """
- return self.rep.to_ddm()
- def to_sdm(self):
- """
- Return a :class:`~.SDM` representation of *self*.
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import QQ
- >>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ)
- >>> sdm = A.to_sdm()
- >>> sdm
- {0: {0: 1}, 1: {1: 2}}
- >>> type(sdm)
- <class 'sympy.polys.matrices.sdm.SDM'>
- See Also
- ========
- to_ddm
- to_sparse
- sympy.polys.matrices.sdm.SDM.to_ddm
- """
- return self.rep.to_sdm()
- @doctest_depends_on(ground_types=['flint'])
- def to_dfm(self):
- """
- Return a :class:`~.DFM` representation of *self*.
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import QQ
- >>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ)
- >>> dfm = A.to_dfm()
- >>> dfm
- [[1, 0], [0, 2]]
- >>> type(dfm)
- <class 'sympy.polys.matrices._dfm.DFM'>
- See Also
- ========
- to_ddm
- to_dense
- DFM
- """
- return self.rep.to_dfm()
- @doctest_depends_on(ground_types=['flint'])
- def to_dfm_or_ddm(self):
- """
- Return a :class:`~.DFM` or :class:`~.DDM` representation of *self*.
- Explanation
- ===========
- The :class:`~.DFM` representation can only be used if the ground types
- are ``flint`` and the ground domain is supported by ``python-flint``.
- This method will return a :class:`~.DFM` representation if possible,
- but will return a :class:`~.DDM` representation otherwise.
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import QQ
- >>> A = DomainMatrix([[1, 0],[0, 2]], (2, 2), QQ)
- >>> dfm = A.to_dfm_or_ddm()
- >>> dfm
- [[1, 0], [0, 2]]
- >>> type(dfm) # Depends on the ground domain and ground types
- <class 'sympy.polys.matrices._dfm.DFM'>
- See Also
- ========
- to_ddm: Always return a :class:`~.DDM` representation.
- to_dfm: Returns a :class:`~.DFM` representation or raise an error.
- to_dense: Convert internally to a :class:`~.DFM` or :class:`~.DDM`
- DFM: The :class:`~.DFM` dense FLINT matrix representation.
- DDM: The Python :class:`~.DDM` dense domain matrix representation.
- """
- return self.rep.to_dfm_or_ddm()
- @classmethod
- def _unify_domain(cls, *matrices):
- """Convert matrices to a common domain"""
- domains = {matrix.domain for matrix in matrices}
- if len(domains) == 1:
- return matrices
- domain = reduce(lambda x, y: x.unify(y), domains)
- return tuple(matrix.convert_to(domain) for matrix in matrices)
- @classmethod
- def _unify_fmt(cls, *matrices, fmt=None):
- """Convert matrices to the same format.
- If all matrices have the same format, then return unmodified.
- Otherwise convert both to the preferred format given as *fmt* which
- should be 'dense' or 'sparse'.
- """
- formats = {matrix.rep.fmt for matrix in matrices}
- if len(formats) == 1:
- return matrices
- if fmt == 'sparse':
- return tuple(matrix.to_sparse() for matrix in matrices)
- elif fmt == 'dense':
- return tuple(matrix.to_dense() for matrix in matrices)
- else:
- raise ValueError("fmt should be 'sparse' or 'dense'")
- def unify(self, *others, fmt=None):
- """
- Unifies the domains and the format of self and other
- matrices.
- Parameters
- ==========
- others : DomainMatrix
- fmt: string 'dense', 'sparse' or `None` (default)
- The preferred format to convert to if self and other are not
- already in the same format. If `None` or not specified then no
- conversion if performed.
- Returns
- =======
- Tuple[DomainMatrix]
- Matrices with unified domain and format
- Examples
- ========
- Unify the domain of DomainMatrix that have different domains:
- >>> from sympy import ZZ, QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
- >>> B = DomainMatrix([[QQ(1, 2), QQ(2)]], (1, 2), QQ)
- >>> Aq, Bq = A.unify(B)
- >>> Aq
- DomainMatrix([[1, 2]], (1, 2), QQ)
- >>> Bq
- DomainMatrix([[1/2, 2]], (1, 2), QQ)
- Unify the format (dense or sparse):
- >>> A = DomainMatrix([[ZZ(1), ZZ(2)]], (1, 2), ZZ)
- >>> B = DomainMatrix({0:{0: ZZ(1)}}, (2, 2), ZZ)
- >>> B.rep
- {0: {0: 1}}
- >>> A2, B2 = A.unify(B, fmt='dense')
- >>> B2.rep
- [[1, 0], [0, 0]]
- See Also
- ========
- convert_to, to_dense, to_sparse
- """
- matrices = (self,) + others
- matrices = DomainMatrix._unify_domain(*matrices)
- if fmt is not None:
- matrices = DomainMatrix._unify_fmt(*matrices, fmt=fmt)
- return matrices
- def to_Matrix(self):
- r"""
- Convert DomainMatrix to Matrix
- Returns
- =======
- Matrix
- MutableDenseMatrix for the DomainMatrix
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> A.to_Matrix()
- Matrix([
- [1, 2],
- [3, 4]])
- See Also
- ========
- from_Matrix
- """
- from sympy.matrices.dense import MutableDenseMatrix
- # XXX: If the internal representation of RepMatrix changes then this
- # might need to be changed also.
- if self.domain in (ZZ, QQ, EXRAW):
- if self.rep.fmt == "sparse":
- rep = self.copy()
- else:
- rep = self.to_sparse()
- else:
- rep = self.convert_to(EXRAW).to_sparse()
- return MutableDenseMatrix._fromrep(rep)
- def to_list(self):
- """
- Convert :class:`DomainMatrix` to list of lists.
- See Also
- ========
- from_list
- to_list_flat
- to_flat_nz
- to_dok
- """
- return self.rep.to_list()
- def to_list_flat(self):
- """
- Convert :class:`DomainMatrix` to flat list.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> A.to_list_flat()
- [1, 2, 3, 4]
- See Also
- ========
- from_list_flat
- to_list
- to_flat_nz
- to_dok
- """
- return self.rep.to_list_flat()
- @classmethod
- def from_list_flat(cls, elements, shape, domain):
- """
- Create :class:`DomainMatrix` from flat list.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> element_list = [ZZ(1), ZZ(2), ZZ(3), ZZ(4)]
- >>> A = DomainMatrix.from_list_flat(element_list, (2, 2), ZZ)
- >>> A
- DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
- >>> A == A.from_list_flat(A.to_list_flat(), A.shape, A.domain)
- True
- See Also
- ========
- to_list_flat
- """
- ddm = DDM.from_list_flat(elements, shape, domain)
- return cls.from_rep(ddm.to_dfm_or_ddm())
- def to_flat_nz(self):
- """
- Convert :class:`DomainMatrix` to list of nonzero elements and data.
- Explanation
- ===========
- Returns a tuple ``(elements, data)`` where ``elements`` is a list of
- elements of the matrix with zeros possibly excluded. The matrix can be
- reconstructed by passing these to :meth:`from_flat_nz`. The idea is to
- be able to modify a flat list of the elements and then create a new
- matrix of the same shape with the modified elements in the same
- positions.
- The format of ``data`` differs depending on whether the underlying
- representation is dense or sparse but either way it represents the
- positions of the elements in the list in a way that
- :meth:`from_flat_nz` can use to reconstruct the matrix. The
- :meth:`from_flat_nz` method should be called on the same
- :class:`DomainMatrix` that was used to call :meth:`to_flat_nz`.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> elements, data = A.to_flat_nz()
- >>> elements
- [1, 2, 3, 4]
- >>> A == A.from_flat_nz(elements, data, A.domain)
- True
- Create a matrix with the elements doubled:
- >>> elements_doubled = [2*x for x in elements]
- >>> A2 = A.from_flat_nz(elements_doubled, data, A.domain)
- >>> A2 == 2*A
- True
- See Also
- ========
- from_flat_nz
- """
- return self.rep.to_flat_nz()
- def from_flat_nz(self, elements, data, domain):
- """
- Reconstruct :class:`DomainMatrix` after calling :meth:`to_flat_nz`.
- See :meth:`to_flat_nz` for explanation.
- See Also
- ========
- to_flat_nz
- """
- rep = self.rep.from_flat_nz(elements, data, domain)
- return self.from_rep(rep)
- def to_dod(self):
- """
- Convert :class:`DomainMatrix` to dictionary of dictionaries (dod) format.
- Explanation
- ===========
- Returns a dictionary of dictionaries representing the matrix.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[ZZ(1), ZZ(2), ZZ(0)], [ZZ(3), ZZ(0), ZZ(4)]], ZZ)
- >>> A.to_dod()
- {0: {0: 1, 1: 2}, 1: {0: 3, 2: 4}}
- >>> A.to_sparse() == A.from_dod(A.to_dod(), A.shape, A.domain)
- True
- >>> A == A.from_dod_like(A.to_dod())
- True
- See Also
- ========
- from_dod
- from_dod_like
- to_dok
- to_list
- to_list_flat
- to_flat_nz
- sympy.matrices.matrixbase.MatrixBase.todod
- """
- return self.rep.to_dod()
- @classmethod
- def from_dod(cls, dod, shape, domain):
- """
- Create sparse :class:`DomainMatrix` from dict of dict (dod) format.
- See :meth:`to_dod` for explanation.
- See Also
- ========
- to_dod
- from_dod_like
- """
- return cls.from_rep(SDM.from_dod(dod, shape, domain))
- def from_dod_like(self, dod, domain=None):
- """
- Create :class:`DomainMatrix` like ``self`` from dict of dict (dod) format.
- See :meth:`to_dod` for explanation.
- See Also
- ========
- to_dod
- from_dod
- """
- if domain is None:
- domain = self.domain
- return self.from_rep(self.rep.from_dod(dod, self.shape, domain))
- def to_dok(self):
- """
- Convert :class:`DomainMatrix` to dictionary of keys (dok) format.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(0)],
- ... [ZZ(0), ZZ(4)]], (2, 2), ZZ)
- >>> A.to_dok()
- {(0, 0): 1, (1, 1): 4}
- The matrix can be reconstructed by calling :meth:`from_dok` although
- the reconstructed matrix will always be in sparse format:
- >>> A.to_sparse() == A.from_dok(A.to_dok(), A.shape, A.domain)
- True
- See Also
- ========
- from_dok
- to_list
- to_list_flat
- to_flat_nz
- """
- return self.rep.to_dok()
- @classmethod
- def from_dok(cls, dok, shape, domain):
- """
- Create :class:`DomainMatrix` from dictionary of keys (dok) format.
- See :meth:`to_dok` for explanation.
- See Also
- ========
- to_dok
- """
- return cls.from_rep(SDM.from_dok(dok, shape, domain))
- def iter_values(self):
- """
- Iterate over nonzero elements of the matrix.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> list(A.iter_values())
- [1, 3, 4]
- See Also
- ========
- iter_items
- to_list_flat
- sympy.matrices.matrixbase.MatrixBase.iter_values
- """
- return self.rep.iter_values()
- def iter_items(self):
- """
- Iterate over indices and values of nonzero elements of the matrix.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([[ZZ(1), ZZ(0)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> list(A.iter_items())
- [((0, 0), 1), ((1, 0), 3), ((1, 1), 4)]
- See Also
- ========
- iter_values
- to_dok
- sympy.matrices.matrixbase.MatrixBase.iter_items
- """
- return self.rep.iter_items()
- def nnz(self):
- """
- Number of nonzero elements in the matrix.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[1, 0], [0, 4]], ZZ)
- >>> A.nnz()
- 2
- """
- return self.rep.nnz()
- def __repr__(self):
- return 'DomainMatrix(%s, %r, %r)' % (str(self.rep), self.shape, self.domain)
- def transpose(self):
- """Matrix transpose of ``self``"""
- return self.from_rep(self.rep.transpose())
- def flat(self):
- rows, cols = self.shape
- return [self[i,j].element for i in range(rows) for j in range(cols)]
- @property
- def is_zero_matrix(self):
- return self.rep.is_zero_matrix()
- @property
- def is_upper(self):
- """
- Says whether this matrix is upper-triangular. True can be returned
- even if the matrix is not square.
- """
- return self.rep.is_upper()
- @property
- def is_lower(self):
- """
- Says whether this matrix is lower-triangular. True can be returned
- even if the matrix is not square.
- """
- return self.rep.is_lower()
- @property
- def is_diagonal(self):
- """
- True if the matrix is diagonal.
- Can return true for non-square matrices. A matrix is diagonal if
- ``M[i,j] == 0`` whenever ``i != j``.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> M = DM([[ZZ(1), ZZ(0)], [ZZ(0), ZZ(1)]], ZZ)
- >>> M.is_diagonal
- True
- See Also
- ========
- is_upper
- is_lower
- is_square
- diagonal
- """
- return self.rep.is_diagonal()
- def diagonal(self):
- """
- Get the diagonal entries of the matrix as a list.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> M = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
- >>> M.diagonal()
- [1, 4]
- See Also
- ========
- is_diagonal
- diag
- """
- return self.rep.diagonal()
- @property
- def is_square(self):
- """
- True if the matrix is square.
- """
- return self.shape[0] == self.shape[1]
- def rank(self):
- rref, pivots = self.rref()
- return len(pivots)
- def hstack(A, *B):
- r"""Horizontally stack the given matrices.
- Parameters
- ==========
- B: DomainMatrix
- Matrices to stack horizontally.
- Returns
- =======
- DomainMatrix
- DomainMatrix by stacking horizontally.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
- >>> A.hstack(B)
- DomainMatrix([[1, 2, 5, 6], [3, 4, 7, 8]], (2, 4), ZZ)
- >>> C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
- >>> A.hstack(B, C)
- DomainMatrix([[1, 2, 5, 6, 9, 10], [3, 4, 7, 8, 11, 12]], (2, 6), ZZ)
- See Also
- ========
- unify
- """
- A, *B = A.unify(*B, fmt=A.rep.fmt)
- return DomainMatrix.from_rep(A.rep.hstack(*(Bk.rep for Bk in B)))
- def vstack(A, *B):
- r"""Vertically stack the given matrices.
- Parameters
- ==========
- B: DomainMatrix
- Matrices to stack vertically.
- Returns
- =======
- DomainMatrix
- DomainMatrix by stacking vertically.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> B = DomainMatrix([[ZZ(5), ZZ(6)], [ZZ(7), ZZ(8)]], (2, 2), ZZ)
- >>> A.vstack(B)
- DomainMatrix([[1, 2], [3, 4], [5, 6], [7, 8]], (4, 2), ZZ)
- >>> C = DomainMatrix([[ZZ(9), ZZ(10)], [ZZ(11), ZZ(12)]], (2, 2), ZZ)
- >>> A.vstack(B, C)
- DomainMatrix([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12]], (6, 2), ZZ)
- See Also
- ========
- unify
- """
- A, *B = A.unify(*B, fmt='dense')
- return DomainMatrix.from_rep(A.rep.vstack(*(Bk.rep for Bk in B)))
- def applyfunc(self, func, domain=None):
- if domain is None:
- domain = self.domain
- return self.from_rep(self.rep.applyfunc(func, domain))
- def __add__(A, B):
- if not isinstance(B, DomainMatrix):
- return NotImplemented
- A, B = A.unify(B, fmt='dense')
- return A.add(B)
- def __sub__(A, B):
- if not isinstance(B, DomainMatrix):
- return NotImplemented
- A, B = A.unify(B, fmt='dense')
- return A.sub(B)
- def __neg__(A):
- return A.neg()
- def __mul__(A, B):
- """A * B"""
- if isinstance(B, DomainMatrix):
- A, B = A.unify(B, fmt='dense')
- return A.matmul(B)
- elif B in A.domain:
- return A.scalarmul(B)
- elif isinstance(B, DomainScalar):
- A, B = A.unify(B)
- return A.scalarmul(B.element)
- else:
- return NotImplemented
- def __rmul__(A, B):
- if B in A.domain:
- return A.rscalarmul(B)
- elif isinstance(B, DomainScalar):
- A, B = A.unify(B)
- return A.rscalarmul(B.element)
- else:
- return NotImplemented
- def __pow__(A, n):
- """A ** n"""
- if not isinstance(n, int):
- return NotImplemented
- return A.pow(n)
- def _check(a, op, b, ashape, bshape):
- if a.domain != b.domain:
- msg = "Domain mismatch: %s %s %s" % (a.domain, op, b.domain)
- raise DMDomainError(msg)
- if ashape != bshape:
- msg = "Shape mismatch: %s %s %s" % (a.shape, op, b.shape)
- raise DMShapeError(msg)
- if a.rep.fmt != b.rep.fmt:
- msg = "Format mismatch: %s %s %s" % (a.rep.fmt, op, b.rep.fmt)
- raise DMFormatError(msg)
- if type(a.rep) != type(b.rep):
- msg = "Type mismatch: %s %s %s" % (type(a.rep), op, type(b.rep))
- raise DMFormatError(msg)
- def add(A, B):
- r"""
- Adds two DomainMatrix matrices of the same Domain
- Parameters
- ==========
- A, B: DomainMatrix
- matrices to add
- Returns
- =======
- DomainMatrix
- DomainMatrix after Addition
- Raises
- ======
- DMShapeError
- If the dimensions of the two DomainMatrix are not equal
- ValueError
- If the domain of the two DomainMatrix are not same
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> B = DomainMatrix([
- ... [ZZ(4), ZZ(3)],
- ... [ZZ(2), ZZ(1)]], (2, 2), ZZ)
- >>> A.add(B)
- DomainMatrix([[5, 5], [5, 5]], (2, 2), ZZ)
- See Also
- ========
- sub, matmul
- """
- A._check('+', B, A.shape, B.shape)
- return A.from_rep(A.rep.add(B.rep))
- def sub(A, B):
- r"""
- Subtracts two DomainMatrix matrices of the same Domain
- Parameters
- ==========
- A, B: DomainMatrix
- matrices to subtract
- Returns
- =======
- DomainMatrix
- DomainMatrix after Subtraction
- Raises
- ======
- DMShapeError
- If the dimensions of the two DomainMatrix are not equal
- ValueError
- If the domain of the two DomainMatrix are not same
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> B = DomainMatrix([
- ... [ZZ(4), ZZ(3)],
- ... [ZZ(2), ZZ(1)]], (2, 2), ZZ)
- >>> A.sub(B)
- DomainMatrix([[-3, -1], [1, 3]], (2, 2), ZZ)
- See Also
- ========
- add, matmul
- """
- A._check('-', B, A.shape, B.shape)
- return A.from_rep(A.rep.sub(B.rep))
- def neg(A):
- r"""
- Returns the negative of DomainMatrix
- Parameters
- ==========
- A : Represents a DomainMatrix
- Returns
- =======
- DomainMatrix
- DomainMatrix after Negation
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> A.neg()
- DomainMatrix([[-1, -2], [-3, -4]], (2, 2), ZZ)
- """
- return A.from_rep(A.rep.neg())
- def mul(A, b):
- r"""
- Performs term by term multiplication for the second DomainMatrix
- w.r.t first DomainMatrix. Returns a DomainMatrix whose rows are
- list of DomainMatrix matrices created after term by term multiplication.
- Parameters
- ==========
- A, B: DomainMatrix
- matrices to multiply term-wise
- Returns
- =======
- DomainMatrix
- DomainMatrix after term by term multiplication
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> b = ZZ(2)
- >>> A.mul(b)
- DomainMatrix([[2, 4], [6, 8]], (2, 2), ZZ)
- See Also
- ========
- matmul
- """
- return A.from_rep(A.rep.mul(b))
- def rmul(A, b):
- return A.from_rep(A.rep.rmul(b))
- def matmul(A, B):
- r"""
- Performs matrix multiplication of two DomainMatrix matrices
- Parameters
- ==========
- A, B: DomainMatrix
- to multiply
- Returns
- =======
- DomainMatrix
- DomainMatrix after multiplication
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> B = DomainMatrix([
- ... [ZZ(1), ZZ(1)],
- ... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
- >>> A.matmul(B)
- DomainMatrix([[1, 3], [3, 7]], (2, 2), ZZ)
- See Also
- ========
- mul, pow, add, sub
- """
- A._check('*', B, A.shape[1], B.shape[0])
- return A.from_rep(A.rep.matmul(B.rep))
- def _scalarmul(A, lamda, reverse):
- if lamda == A.domain.zero:
- return DomainMatrix.zeros(A.shape, A.domain)
- elif lamda == A.domain.one:
- return A.copy()
- elif reverse:
- return A.rmul(lamda)
- else:
- return A.mul(lamda)
- def scalarmul(A, lamda):
- return A._scalarmul(lamda, reverse=False)
- def rscalarmul(A, lamda):
- return A._scalarmul(lamda, reverse=True)
- def mul_elementwise(A, B):
- assert A.domain == B.domain
- return A.from_rep(A.rep.mul_elementwise(B.rep))
- def __truediv__(A, lamda):
- """ Method for Scalar Division"""
- if isinstance(lamda, int) or ZZ.of_type(lamda):
- lamda = DomainScalar(ZZ(lamda), ZZ)
- elif A.domain.is_Field and lamda in A.domain:
- K = A.domain
- lamda = DomainScalar(K.convert(lamda), K)
- if not isinstance(lamda, DomainScalar):
- return NotImplemented
- A, lamda = A.to_field().unify(lamda)
- if lamda.element == lamda.domain.zero:
- raise ZeroDivisionError
- if lamda.element == lamda.domain.one:
- return A
- return A.mul(1 / lamda.element)
- def pow(A, n):
- r"""
- Computes A**n
- Parameters
- ==========
- A : DomainMatrix
- n : exponent for A
- Returns
- =======
- DomainMatrix
- DomainMatrix on computing A**n
- Raises
- ======
- NotImplementedError
- if n is negative.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(1)],
- ... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
- >>> A.pow(2)
- DomainMatrix([[1, 2], [0, 1]], (2, 2), ZZ)
- See Also
- ========
- matmul
- """
- nrows, ncols = A.shape
- if nrows != ncols:
- raise DMNonSquareMatrixError('Power of a nonsquare matrix')
- if n < 0:
- raise NotImplementedError('Negative powers')
- elif n == 0:
- return A.eye(nrows, A.domain)
- elif n == 1:
- return A
- elif n % 2 == 1:
- return A * A**(n - 1)
- else:
- sqrtAn = A ** (n // 2)
- return sqrtAn * sqrtAn
- def scc(self):
- """Compute the strongly connected components of a DomainMatrix
- Explanation
- ===========
- A square matrix can be considered as the adjacency matrix for a
- directed graph where the row and column indices are the vertices. In
- this graph if there is an edge from vertex ``i`` to vertex ``j`` if
- ``M[i, j]`` is nonzero. This routine computes the strongly connected
- components of that graph which are subsets of the rows and columns that
- are connected by some nonzero element of the matrix. The strongly
- connected components are useful because many operations such as the
- determinant can be computed by working with the submatrices
- corresponding to each component.
- Examples
- ========
- Find the strongly connected components of a matrix:
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> M = DomainMatrix([[ZZ(1), ZZ(0), ZZ(2)],
- ... [ZZ(0), ZZ(3), ZZ(0)],
- ... [ZZ(4), ZZ(6), ZZ(5)]], (3, 3), ZZ)
- >>> M.scc()
- [[1], [0, 2]]
- Compute the determinant from the components:
- >>> MM = M.to_Matrix()
- >>> MM
- Matrix([
- [1, 0, 2],
- [0, 3, 0],
- [4, 6, 5]])
- >>> MM[[1], [1]]
- Matrix([[3]])
- >>> MM[[0, 2], [0, 2]]
- Matrix([
- [1, 2],
- [4, 5]])
- >>> MM.det()
- -9
- >>> MM[[1], [1]].det() * MM[[0, 2], [0, 2]].det()
- -9
- The components are given in reverse topological order and represent a
- permutation of the rows and columns that will bring the matrix into
- block lower-triangular form:
- >>> MM[[1, 0, 2], [1, 0, 2]]
- Matrix([
- [3, 0, 0],
- [0, 1, 2],
- [6, 4, 5]])
- Returns
- =======
- List of lists of integers
- Each list represents a strongly connected component.
- See also
- ========
- sympy.matrices.matrixbase.MatrixBase.strongly_connected_components
- sympy.utilities.iterables.strongly_connected_components
- """
- if not self.is_square:
- raise DMNonSquareMatrixError('Matrix must be square for scc')
- return self.rep.scc()
- def clear_denoms(self, convert=False):
- """
- Clear denominators, but keep the domain unchanged.
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[(1,2), (1,3)], [(1,4), (1,5)]], QQ)
- >>> den, Anum = A.clear_denoms()
- >>> den.to_sympy()
- 60
- >>> Anum.to_Matrix()
- Matrix([
- [30, 20],
- [15, 12]])
- >>> den * A == Anum
- True
- The numerator matrix will be in the same domain as the original matrix
- unless ``convert`` is set to ``True``:
- >>> A.clear_denoms()[1].domain
- QQ
- >>> A.clear_denoms(convert=True)[1].domain
- ZZ
- The denominator is always in the associated ring:
- >>> A.clear_denoms()[0].domain
- ZZ
- >>> A.domain.get_ring()
- ZZ
- See Also
- ========
- sympy.polys.polytools.Poly.clear_denoms
- clear_denoms_rowwise
- """
- elems0, data = self.to_flat_nz()
- K0 = self.domain
- K1 = K0.get_ring() if K0.has_assoc_Ring else K0
- den, elems1 = dup_clear_denoms(elems0, K0, K1, convert=convert)
- if convert:
- Kden, Knum = K1, K1
- else:
- Kden, Knum = K1, K0
- den = DomainScalar(den, Kden)
- num = self.from_flat_nz(elems1, data, Knum)
- return den, num
- def clear_denoms_rowwise(self, convert=False):
- """
- Clear denominators from each row of the matrix.
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[(1,2), (1,3), (1,4)], [(1,5), (1,6), (1,7)]], QQ)
- >>> den, Anum = A.clear_denoms_rowwise()
- >>> den.to_Matrix()
- Matrix([
- [12, 0],
- [ 0, 210]])
- >>> Anum.to_Matrix()
- Matrix([
- [ 6, 4, 3],
- [42, 35, 30]])
- The denominator matrix is a diagonal matrix with the denominators of
- each row on the diagonal. The invariants are:
- >>> den * A == Anum
- True
- >>> A == den.to_field().inv() * Anum
- True
- The numerator matrix will be in the same domain as the original matrix
- unless ``convert`` is set to ``True``:
- >>> A.clear_denoms_rowwise()[1].domain
- QQ
- >>> A.clear_denoms_rowwise(convert=True)[1].domain
- ZZ
- The domain of the denominator matrix is the associated ring:
- >>> A.clear_denoms_rowwise()[0].domain
- ZZ
- See Also
- ========
- sympy.polys.polytools.Poly.clear_denoms
- clear_denoms
- """
- dod = self.to_dod()
- K0 = self.domain
- K1 = K0.get_ring() if K0.has_assoc_Ring else K0
- diagonals = [K0.one] * self.shape[0]
- dod_num = {}
- for i, rowi in dod.items():
- indices, elems = zip(*rowi.items())
- den, elems_num = dup_clear_denoms(elems, K0, K1, convert=convert)
- rowi_num = dict(zip(indices, elems_num))
- diagonals[i] = den
- dod_num[i] = rowi_num
- if convert:
- Kden, Knum = K1, K1
- else:
- Kden, Knum = K1, K0
- den = self.diag(diagonals, Kden)
- num = self.from_dod_like(dod_num, Knum)
- return den, num
- def cancel_denom(self, denom):
- """
- Cancel factors between a matrix and a denominator.
- Returns a matrix and denominator on lowest terms.
- Requires ``gcd`` in the ground domain.
- Methods like :meth:`solve_den`, :meth:`inv_den` and :meth:`rref_den`
- return a matrix and denominator but not necessarily on lowest terms.
- Reduction to lowest terms without fractions can be performed with
- :meth:`cancel_denom`.
- Examples
- ========
- >>> from sympy.polys.matrices import DM
- >>> from sympy import ZZ
- >>> M = DM([[2, 2, 0],
- ... [0, 2, 2],
- ... [0, 0, 2]], ZZ)
- >>> Minv, den = M.inv_den()
- >>> Minv.to_Matrix()
- Matrix([
- [1, -1, 1],
- [0, 1, -1],
- [0, 0, 1]])
- >>> den
- 2
- >>> Minv_reduced, den_reduced = Minv.cancel_denom(den)
- >>> Minv_reduced.to_Matrix()
- Matrix([
- [1, -1, 1],
- [0, 1, -1],
- [0, 0, 1]])
- >>> den_reduced
- 2
- >>> Minv_reduced.to_field() / den_reduced == Minv.to_field() / den
- True
- The denominator is made canonical with respect to units (e.g. a
- negative denominator is made positive):
- >>> M = DM([[2, 2, 0]], ZZ)
- >>> den = ZZ(-4)
- >>> M.cancel_denom(den)
- (DomainMatrix([[-1, -1, 0]], (1, 3), ZZ), 2)
- Any factor common to _all_ elements will be cancelled but there can
- still be factors in common between _some_ elements of the matrix and
- the denominator. To cancel factors between each element and the
- denominator, use :meth:`cancel_denom_elementwise` or otherwise convert
- to a field and use division:
- >>> M = DM([[4, 6]], ZZ)
- >>> den = ZZ(12)
- >>> M.cancel_denom(den)
- (DomainMatrix([[2, 3]], (1, 2), ZZ), 6)
- >>> numers, denoms = M.cancel_denom_elementwise(den)
- >>> numers
- DomainMatrix([[1, 1]], (1, 2), ZZ)
- >>> denoms
- DomainMatrix([[3, 2]], (1, 2), ZZ)
- >>> M.to_field() / den
- DomainMatrix([[1/3, 1/2]], (1, 2), QQ)
- See Also
- ========
- solve_den
- inv_den
- rref_den
- cancel_denom_elementwise
- """
- M = self
- K = self.domain
- if K.is_zero(denom):
- raise ZeroDivisionError('denominator is zero')
- elif K.is_one(denom):
- return (M.copy(), denom)
- elements, data = M.to_flat_nz()
- # First canonicalize the denominator (e.g. multiply by -1).
- if K.is_negative(denom):
- u = -K.one
- else:
- u = K.canonical_unit(denom)
- # Often after e.g. solve_den the denominator will be much more
- # complicated than the elements of the numerator. Hopefully it will be
- # quicker to find the gcd of the numerator and if there is no content
- # then we do not need to look at the denominator at all.
- content = dup_content(elements, K)
- common = K.gcd(content, denom)
- if not K.is_one(content):
- common = K.gcd(content, denom)
- if not K.is_one(common):
- elements = dup_quo_ground(elements, common, K)
- denom = K.quo(denom, common)
- if not K.is_one(u):
- elements = dup_mul_ground(elements, u, K)
- denom = u * denom
- elif K.is_one(common):
- return (M.copy(), denom)
- M_cancelled = M.from_flat_nz(elements, data, K)
- return M_cancelled, denom
- def cancel_denom_elementwise(self, denom):
- """
- Cancel factors between the elements of a matrix and a denominator.
- Returns a matrix of numerators and matrix of denominators.
- Requires ``gcd`` in the ground domain.
- Examples
- ========
- >>> from sympy.polys.matrices import DM
- >>> from sympy import ZZ
- >>> M = DM([[2, 3], [4, 12]], ZZ)
- >>> denom = ZZ(6)
- >>> numers, denoms = M.cancel_denom_elementwise(denom)
- >>> numers.to_Matrix()
- Matrix([
- [1, 1],
- [2, 2]])
- >>> denoms.to_Matrix()
- Matrix([
- [3, 2],
- [3, 1]])
- >>> M_frac = (M.to_field() / denom).to_Matrix()
- >>> M_frac
- Matrix([
- [1/3, 1/2],
- [2/3, 2]])
- >>> denoms_inverted = denoms.to_Matrix().applyfunc(lambda e: 1/e)
- >>> numers.to_Matrix().multiply_elementwise(denoms_inverted) == M_frac
- True
- Use :meth:`cancel_denom` to cancel factors between the matrix and the
- denominator while preserving the form of a matrix with a scalar
- denominator.
- See Also
- ========
- cancel_denom
- """
- K = self.domain
- M = self
- if K.is_zero(denom):
- raise ZeroDivisionError('denominator is zero')
- elif K.is_one(denom):
- M_numers = M.copy()
- M_denoms = M.ones(M.shape, M.domain)
- return (M_numers, M_denoms)
- elements, data = M.to_flat_nz()
- cofactors = [K.cofactors(numer, denom) for numer in elements]
- gcds, numers, denoms = zip(*cofactors)
- M_numers = M.from_flat_nz(list(numers), data, K)
- M_denoms = M.from_flat_nz(list(denoms), data, K)
- return (M_numers, M_denoms)
- def content(self):
- """
- Return the gcd of the elements of the matrix.
- Requires ``gcd`` in the ground domain.
- Examples
- ========
- >>> from sympy.polys.matrices import DM
- >>> from sympy import ZZ
- >>> M = DM([[2, 4], [4, 12]], ZZ)
- >>> M.content()
- 2
- See Also
- ========
- primitive
- cancel_denom
- """
- K = self.domain
- elements, _ = self.to_flat_nz()
- return dup_content(elements, K)
- def primitive(self):
- """
- Factor out gcd of the elements of a matrix.
- Requires ``gcd`` in the ground domain.
- Examples
- ========
- >>> from sympy.polys.matrices import DM
- >>> from sympy import ZZ
- >>> M = DM([[2, 4], [4, 12]], ZZ)
- >>> content, M_primitive = M.primitive()
- >>> content
- 2
- >>> M_primitive
- DomainMatrix([[1, 2], [2, 6]], (2, 2), ZZ)
- >>> content * M_primitive == M
- True
- >>> M_primitive.content() == ZZ(1)
- True
- See Also
- ========
- content
- cancel_denom
- """
- K = self.domain
- elements, data = self.to_flat_nz()
- content, prims = dup_primitive(elements, K)
- M_primitive = self.from_flat_nz(prims, data, K)
- return content, M_primitive
- def rref(self, *, method='auto'):
- r"""
- Returns reduced-row echelon form (RREF) and list of pivots.
- If the domain is not a field then it will be converted to a field. See
- :meth:`rref_den` for the fraction-free version of this routine that
- returns RREF with denominator instead.
- The domain must either be a field or have an associated fraction field
- (see :meth:`to_field`).
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [QQ(2), QQ(-1), QQ(0)],
- ... [QQ(-1), QQ(2), QQ(-1)],
- ... [QQ(0), QQ(0), QQ(2)]], (3, 3), QQ)
- >>> rref_matrix, rref_pivots = A.rref()
- >>> rref_matrix
- DomainMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]], (3, 3), QQ)
- >>> rref_pivots
- (0, 1, 2)
- Parameters
- ==========
- method : str, optional (default: 'auto')
- The method to use to compute the RREF. The default is ``'auto'``,
- which will attempt to choose the fastest method. The other options
- are:
- - ``A.rref(method='GJ')`` uses Gauss-Jordan elimination with
- division. If the domain is not a field then it will be converted
- to a field with :meth:`to_field` first and RREF will be computed
- by inverting the pivot elements in each row. This is most
- efficient for very sparse matrices or for matrices whose elements
- have complex denominators.
- - ``A.rref(method='FF')`` uses fraction-free Gauss-Jordan
- elimination. Elimination is performed using exact division
- (``exquo``) to control the growth of the coefficients. In this
- case the current domain is always used for elimination but if
- the domain is not a field then it will be converted to a field
- at the end and divided by the denominator. This is most efficient
- for dense matrices or for matrices with simple denominators.
- - ``A.rref(method='CD')`` clears the denominators before using
- fraction-free Gauss-Jordan elimination in the associated ring.
- This is most efficient for dense matrices with very simple
- denominators.
- - ``A.rref(method='GJ_dense')``, ``A.rref(method='FF_dense')``, and
- ``A.rref(method='CD_dense')`` are the same as the above methods
- except that the dense implementations of the algorithms are used.
- By default ``A.rref(method='auto')`` will usually choose the
- sparse implementations for RREF.
- Regardless of which algorithm is used the returned matrix will
- always have the same format (sparse or dense) as the input and its
- domain will always be the field of fractions of the input domain.
- Returns
- =======
- (DomainMatrix, list)
- reduced-row echelon form and list of pivots for the DomainMatrix
- See Also
- ========
- rref_den
- RREF with denominator
- sympy.polys.matrices.sdm.sdm_irref
- Sparse implementation of ``method='GJ'``.
- sympy.polys.matrices.sdm.sdm_rref_den
- Sparse implementation of ``method='FF'`` and ``method='CD'``.
- sympy.polys.matrices.dense.ddm_irref
- Dense implementation of ``method='GJ'``.
- sympy.polys.matrices.dense.ddm_irref_den
- Dense implementation of ``method='FF'`` and ``method='CD'``.
- clear_denoms
- Clear denominators from a matrix, used by ``method='CD'`` and
- by ``method='GJ'`` when the original domain is not a field.
- """
- return _dm_rref(self, method=method)
- def rref_den(self, *, method='auto', keep_domain=True):
- r"""
- Returns reduced-row echelon form with denominator and list of pivots.
- Requires exact division in the ground domain (``exquo``).
- Examples
- ========
- >>> from sympy import ZZ, QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(2), ZZ(-1), ZZ(0)],
- ... [ZZ(-1), ZZ(2), ZZ(-1)],
- ... [ZZ(0), ZZ(0), ZZ(2)]], (3, 3), ZZ)
- >>> A_rref, denom, pivots = A.rref_den()
- >>> A_rref
- DomainMatrix([[6, 0, 0], [0, 6, 0], [0, 0, 6]], (3, 3), ZZ)
- >>> denom
- 6
- >>> pivots
- (0, 1, 2)
- >>> A_rref.to_field() / denom
- DomainMatrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]], (3, 3), QQ)
- >>> A_rref.to_field() / denom == A.convert_to(QQ).rref()[0]
- True
- Parameters
- ==========
- method : str, optional (default: 'auto')
- The method to use to compute the RREF. The default is ``'auto'``,
- which will attempt to choose the fastest method. The other options
- are:
- - ``A.rref(method='FF')`` uses fraction-free Gauss-Jordan
- elimination. Elimination is performed using exact division
- (``exquo``) to control the growth of the coefficients. In this
- case the current domain is always used for elimination and the
- result is always returned as a matrix over the current domain.
- This is most efficient for dense matrices or for matrices with
- simple denominators.
- - ``A.rref(method='CD')`` clears denominators before using
- fraction-free Gauss-Jordan elimination in the associated ring.
- The result will be converted back to the original domain unless
- ``keep_domain=False`` is passed in which case the result will be
- over the ring used for elimination. This is most efficient for
- dense matrices with very simple denominators.
- - ``A.rref(method='GJ')`` uses Gauss-Jordan elimination with
- division. If the domain is not a field then it will be converted
- to a field with :meth:`to_field` first and RREF will be computed
- by inverting the pivot elements in each row. The result is
- converted back to the original domain by clearing denominators
- unless ``keep_domain=False`` is passed in which case the result
- will be over the field used for elimination. This is most
- efficient for very sparse matrices or for matrices whose elements
- have complex denominators.
- - ``A.rref(method='GJ_dense')``, ``A.rref(method='FF_dense')``, and
- ``A.rref(method='CD_dense')`` are the same as the above methods
- except that the dense implementations of the algorithms are used.
- By default ``A.rref(method='auto')`` will usually choose the
- sparse implementations for RREF.
- Regardless of which algorithm is used the returned matrix will
- always have the same format (sparse or dense) as the input and if
- ``keep_domain=True`` its domain will always be the same as the
- input.
- keep_domain : bool, optional
- If True (the default), the domain of the returned matrix and
- denominator are the same as the domain of the input matrix. If
- False, the domain of the returned matrix might be changed to an
- associated ring or field if the algorithm used a different domain.
- This is useful for efficiency if the caller does not need the
- result to be in the original domain e.g. it avoids clearing
- denominators in the case of ``A.rref(method='GJ')``.
- Returns
- =======
- (DomainMatrix, scalar, list)
- Reduced-row echelon form, denominator and list of pivot indices.
- See Also
- ========
- rref
- RREF without denominator for field domains.
- sympy.polys.matrices.sdm.sdm_irref
- Sparse implementation of ``method='GJ'``.
- sympy.polys.matrices.sdm.sdm_rref_den
- Sparse implementation of ``method='FF'`` and ``method='CD'``.
- sympy.polys.matrices.dense.ddm_irref
- Dense implementation of ``method='GJ'``.
- sympy.polys.matrices.dense.ddm_irref_den
- Dense implementation of ``method='FF'`` and ``method='CD'``.
- clear_denoms
- Clear denominators from a matrix, used by ``method='CD'``.
- """
- return _dm_rref_den(self, method=method, keep_domain=keep_domain)
- def columnspace(self):
- r"""
- Returns the columnspace for the DomainMatrix
- Returns
- =======
- DomainMatrix
- The columns of this matrix form a basis for the columnspace.
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [QQ(1), QQ(-1)],
- ... [QQ(2), QQ(-2)]], (2, 2), QQ)
- >>> A.columnspace()
- DomainMatrix([[1], [2]], (2, 1), QQ)
- """
- if not self.domain.is_Field:
- raise DMNotAField('Not a field')
- rref, pivots = self.rref()
- rows, cols = self.shape
- return self.extract(range(rows), pivots)
- def rowspace(self):
- r"""
- Returns the rowspace for the DomainMatrix
- Returns
- =======
- DomainMatrix
- The rows of this matrix form a basis for the rowspace.
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [QQ(1), QQ(-1)],
- ... [QQ(2), QQ(-2)]], (2, 2), QQ)
- >>> A.rowspace()
- DomainMatrix([[1, -1]], (1, 2), QQ)
- """
- if not self.domain.is_Field:
- raise DMNotAField('Not a field')
- rref, pivots = self.rref()
- rows, cols = self.shape
- return self.extract(range(len(pivots)), range(cols))
- def nullspace(self, divide_last=False):
- r"""
- Returns the nullspace for the DomainMatrix
- Returns
- =======
- DomainMatrix
- The rows of this matrix form a basis for the nullspace.
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([
- ... [QQ(2), QQ(-2)],
- ... [QQ(4), QQ(-4)]], QQ)
- >>> A.nullspace()
- DomainMatrix([[1, 1]], (1, 2), QQ)
- The returned matrix is a basis for the nullspace:
- >>> A_null = A.nullspace().transpose()
- >>> A * A_null
- DomainMatrix([[0], [0]], (2, 1), QQ)
- >>> rows, cols = A.shape
- >>> nullity = rows - A.rank()
- >>> A_null.shape == (cols, nullity)
- True
- Nullspace can also be computed for non-field rings. If the ring is not
- a field then division is not used. Setting ``divide_last`` to True will
- raise an error in this case:
- >>> from sympy import ZZ
- >>> B = DM([[6, -3],
- ... [4, -2]], ZZ)
- >>> B.nullspace()
- DomainMatrix([[3, 6]], (1, 2), ZZ)
- >>> B.nullspace(divide_last=True)
- Traceback (most recent call last):
- ...
- DMNotAField: Cannot normalize vectors over a non-field
- Over a ring with ``gcd`` defined the nullspace can potentially be
- reduced with :meth:`primitive`:
- >>> B.nullspace().primitive()
- (3, DomainMatrix([[1, 2]], (1, 2), ZZ))
- A matrix over a ring can often be normalized by converting it to a
- field but it is often a bad idea to do so:
- >>> from sympy.abc import a, b, c
- >>> from sympy import Matrix
- >>> M = Matrix([[ a*b, b + c, c],
- ... [ a - b, b*c, c**2],
- ... [a*b + a - b, b*c + b + c, c**2 + c]])
- >>> M.to_DM().domain
- ZZ[a,b,c]
- >>> M.to_DM().nullspace().to_Matrix().transpose()
- Matrix([
- [ c**3],
- [ -a*b*c**2 + a*c - b*c],
- [a*b**2*c - a*b - a*c + b**2 + b*c]])
- The unnormalized form here is nicer than the normalized form that
- spreads a large denominator throughout the matrix:
- >>> M.to_DM().to_field().nullspace(divide_last=True).to_Matrix().transpose()
- Matrix([
- [ c**3/(a*b**2*c - a*b - a*c + b**2 + b*c)],
- [(-a*b*c**2 + a*c - b*c)/(a*b**2*c - a*b - a*c + b**2 + b*c)],
- [ 1]])
- Parameters
- ==========
- divide_last : bool, optional
- If False (the default), the vectors are not normalized and the RREF
- is computed using :meth:`rref_den` and the denominator is
- discarded. If True, then each row is divided by its final element;
- the domain must be a field in this case.
- See Also
- ========
- nullspace_from_rref
- rref
- rref_den
- rowspace
- """
- A = self
- K = A.domain
- if divide_last and not K.is_Field:
- raise DMNotAField("Cannot normalize vectors over a non-field")
- if divide_last:
- A_rref, pivots = A.rref()
- else:
- A_rref, den, pivots = A.rref_den()
- # Ensure that the sign is canonical before discarding the
- # denominator. Then M.nullspace().primitive() is canonical.
- u = K.canonical_unit(den)
- if u != K.one:
- A_rref *= u
- A_null = A_rref.nullspace_from_rref(pivots)
- return A_null
- def nullspace_from_rref(self, pivots=None):
- """
- Compute nullspace from rref and pivots.
- The domain of the matrix can be any domain.
- The matrix must be in reduced row echelon form already. Otherwise the
- result will be incorrect. Use :meth:`rref` or :meth:`rref_den` first
- to get the reduced row echelon form or use :meth:`nullspace` instead.
- See Also
- ========
- nullspace
- rref
- rref_den
- sympy.polys.matrices.sdm.SDM.nullspace_from_rref
- sympy.polys.matrices.ddm.DDM.nullspace_from_rref
- """
- null_rep, nonpivots = self.rep.nullspace_from_rref(pivots)
- return self.from_rep(null_rep)
- def inv(self):
- r"""
- Finds the inverse of the DomainMatrix if exists
- Returns
- =======
- DomainMatrix
- DomainMatrix after inverse
- Raises
- ======
- ValueError
- If the domain of DomainMatrix not a Field
- DMNonSquareMatrixError
- If the DomainMatrix is not a not Square DomainMatrix
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [QQ(2), QQ(-1), QQ(0)],
- ... [QQ(-1), QQ(2), QQ(-1)],
- ... [QQ(0), QQ(0), QQ(2)]], (3, 3), QQ)
- >>> A.inv()
- DomainMatrix([[2/3, 1/3, 1/6], [1/3, 2/3, 1/3], [0, 0, 1/2]], (3, 3), QQ)
- See Also
- ========
- neg
- """
- if not self.domain.is_Field:
- raise DMNotAField('Not a field')
- m, n = self.shape
- if m != n:
- raise DMNonSquareMatrixError
- inv = self.rep.inv()
- return self.from_rep(inv)
- def det(self):
- r"""
- Returns the determinant of a square :class:`DomainMatrix`.
- Returns
- =======
- determinant: DomainElement
- Determinant of the matrix.
- Raises
- ======
- ValueError
- If the domain of DomainMatrix is not a Field
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> A.det()
- -2
- """
- m, n = self.shape
- if m != n:
- raise DMNonSquareMatrixError
- return self.rep.det()
- def adj_det(self):
- """
- Adjugate and determinant of a square :class:`DomainMatrix`.
- Returns
- =======
- (adjugate, determinant) : (DomainMatrix, DomainScalar)
- The adjugate matrix and determinant of this matrix.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], ZZ)
- >>> adjA, detA = A.adj_det()
- >>> adjA
- DomainMatrix([[4, -2], [-3, 1]], (2, 2), ZZ)
- >>> detA
- -2
- See Also
- ========
- adjugate
- Returns only the adjugate matrix.
- det
- Returns only the determinant.
- inv_den
- Returns a matrix/denominator pair representing the inverse matrix
- but perhaps differing from the adjugate and determinant by a common
- factor.
- """
- m, n = self.shape
- I_m = self.eye((m, m), self.domain)
- adjA, detA = self.solve_den_charpoly(I_m, check=False)
- if self.rep.fmt == "dense":
- adjA = adjA.to_dense()
- return adjA, detA
- def adjugate(self):
- """
- Adjugate of a square :class:`DomainMatrix`.
- The adjugate matrix is the transpose of the cofactor matrix and is
- related to the inverse by::
- adj(A) = det(A) * A.inv()
- Unlike the inverse matrix the adjugate matrix can be computed and
- expressed without division or fractions in the ground domain.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
- >>> A.adjugate()
- DomainMatrix([[4, -2], [-3, 1]], (2, 2), ZZ)
- Returns
- =======
- DomainMatrix
- The adjugate matrix of this matrix with the same domain.
- See Also
- ========
- adj_det
- """
- adjA, detA = self.adj_det()
- return adjA
- def inv_den(self, method=None):
- """
- Return the inverse as a :class:`DomainMatrix` with denominator.
- Returns
- =======
- (inv, den) : (:class:`DomainMatrix`, :class:`~.DomainElement`)
- The inverse matrix and its denominator.
- This is more or less equivalent to :meth:`adj_det` except that ``inv``
- and ``den`` are not guaranteed to be the adjugate and inverse. The
- ratio ``inv/den`` is equivalent to ``adj/det`` but some factors
- might be cancelled between ``inv`` and ``den``. In simple cases this
- might just be a minus sign so that ``(inv, den) == (-adj, -det)`` but
- factors more complicated than ``-1`` can also be cancelled.
- Cancellation is not guaranteed to be complete so ``inv`` and ``den``
- may not be on lowest terms. The denominator ``den`` will be zero if and
- only if the determinant is zero.
- If the actual adjugate and determinant are needed, use :meth:`adj_det`
- instead. If the intention is to compute the inverse matrix or solve a
- system of equations then :meth:`inv_den` is more efficient.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(2), ZZ(-1), ZZ(0)],
- ... [ZZ(-1), ZZ(2), ZZ(-1)],
- ... [ZZ(0), ZZ(0), ZZ(2)]], (3, 3), ZZ)
- >>> Ainv, den = A.inv_den()
- >>> den
- 6
- >>> Ainv
- DomainMatrix([[4, 2, 1], [2, 4, 2], [0, 0, 3]], (3, 3), ZZ)
- >>> A * Ainv == den * A.eye(A.shape, A.domain).to_dense()
- True
- Parameters
- ==========
- method : str, optional
- The method to use to compute the inverse. Can be one of ``None``,
- ``'rref'`` or ``'charpoly'``. If ``None`` then the method is
- chosen automatically (see :meth:`solve_den` for details).
- See Also
- ========
- inv
- det
- adj_det
- solve_den
- """
- I = self.eye(self.shape, self.domain)
- return self.solve_den(I, method=method)
- def solve_den(self, b, method=None):
- """
- Solve matrix equation $Ax = b$ without fractions in the ground domain.
- Examples
- ========
- Solve a matrix equation over the integers:
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
- >>> b = DM([[ZZ(5)], [ZZ(6)]], ZZ)
- >>> xnum, xden = A.solve_den(b)
- >>> xden
- -2
- >>> xnum
- DomainMatrix([[8], [-9]], (2, 1), ZZ)
- >>> A * xnum == xden * b
- True
- Solve a matrix equation over a polynomial ring:
- >>> from sympy import ZZ
- >>> from sympy.abc import x, y, z, a, b
- >>> R = ZZ[x, y, z, a, b]
- >>> M = DM([[x*y, x*z], [y*z, x*z]], R)
- >>> b = DM([[a], [b]], R)
- >>> M.to_Matrix()
- Matrix([
- [x*y, x*z],
- [y*z, x*z]])
- >>> b.to_Matrix()
- Matrix([
- [a],
- [b]])
- >>> xnum, xden = M.solve_den(b)
- >>> xden
- x**2*y*z - x*y*z**2
- >>> xnum.to_Matrix()
- Matrix([
- [ a*x*z - b*x*z],
- [-a*y*z + b*x*y]])
- >>> M * xnum == xden * b
- True
- The solution can be expressed over a fraction field which will cancel
- gcds between the denominator and the elements of the numerator:
- >>> xsol = xnum.to_field() / xden
- >>> xsol.to_Matrix()
- Matrix([
- [ (a - b)/(x*y - y*z)],
- [(-a*z + b*x)/(x**2*z - x*z**2)]])
- >>> (M * xsol).to_Matrix() == b.to_Matrix()
- True
- When solving a large system of equations this cancellation step might
- be a lot slower than :func:`solve_den` itself. The solution can also be
- expressed as a ``Matrix`` without attempting any polynomial
- cancellation between the numerator and denominator giving a less
- simplified result more quickly:
- >>> xsol_uncancelled = xnum.to_Matrix() / xnum.domain.to_sympy(xden)
- >>> xsol_uncancelled
- Matrix([
- [ (a*x*z - b*x*z)/(x**2*y*z - x*y*z**2)],
- [(-a*y*z + b*x*y)/(x**2*y*z - x*y*z**2)]])
- >>> from sympy import cancel
- >>> cancel(xsol_uncancelled) == xsol.to_Matrix()
- True
- Parameters
- ==========
- self : :class:`DomainMatrix`
- The ``m x n`` matrix $A$ in the equation $Ax = b$. Underdetermined
- systems are not supported so ``m >= n``: $A$ should be square or
- have more rows than columns.
- b : :class:`DomainMatrix`
- The ``n x m`` matrix $b$ for the rhs.
- cp : list of :class:`~.DomainElement`, optional
- The characteristic polynomial of the matrix $A$. If not given, it
- will be computed using :meth:`charpoly`.
- method: str, optional
- The method to use for solving the system. Can be one of ``None``,
- ``'charpoly'`` or ``'rref'``. If ``None`` (the default) then the
- method will be chosen automatically.
- The ``charpoly`` method uses :meth:`solve_den_charpoly` and can
- only be used if the matrix is square. This method is division free
- and can be used with any domain.
- The ``rref`` method is fraction free but requires exact division
- in the ground domain (``exquo``). This is also suitable for most
- domains. This method can be used with overdetermined systems (more
- equations than unknowns) but not underdetermined systems as a
- unique solution is sought.
- Returns
- =======
- (xnum, xden) : (DomainMatrix, DomainElement)
- The solution of the equation $Ax = b$ as a pair consisting of an
- ``n x m`` matrix numerator ``xnum`` and a scalar denominator
- ``xden``.
- The solution $x$ is given by ``x = xnum / xden``. The division free
- invariant is ``A * xnum == xden * b``. If $A$ is square then the
- denominator ``xden`` will be a divisor of the determinant $det(A)$.
- Raises
- ======
- DMNonInvertibleMatrixError
- If the system $Ax = b$ does not have a unique solution.
- See Also
- ========
- solve_den_charpoly
- solve_den_rref
- inv_den
- """
- m, n = self.shape
- bm, bn = b.shape
- if m != bm:
- raise DMShapeError("Matrix equation shape mismatch.")
- if method is None:
- method = 'rref'
- elif method == 'charpoly' and m != n:
- raise DMNonSquareMatrixError("method='charpoly' requires a square matrix.")
- if method == 'charpoly':
- xnum, xden = self.solve_den_charpoly(b)
- elif method == 'rref':
- xnum, xden = self.solve_den_rref(b)
- else:
- raise DMBadInputError("method should be 'rref' or 'charpoly'")
- return xnum, xden
- def solve_den_rref(self, b):
- """
- Solve matrix equation $Ax = b$ using fraction-free RREF
- Solves the matrix equation $Ax = b$ for $x$ and returns the solution
- as a numerator/denominator pair.
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
- >>> b = DM([[ZZ(5)], [ZZ(6)]], ZZ)
- >>> xnum, xden = A.solve_den_rref(b)
- >>> xden
- -2
- >>> xnum
- DomainMatrix([[8], [-9]], (2, 1), ZZ)
- >>> A * xnum == xden * b
- True
- See Also
- ========
- solve_den
- solve_den_charpoly
- """
- A = self
- m, n = A.shape
- bm, bn = b.shape
- if m != bm:
- raise DMShapeError("Matrix equation shape mismatch.")
- if m < n:
- raise DMShapeError("Underdetermined matrix equation.")
- Aaug = A.hstack(b)
- Aaug_rref, denom, pivots = Aaug.rref_den()
- # XXX: We check here if there are pivots after the last column. If
- # there were than it possibly means that rref_den performed some
- # unnecessary elimination. It would be better if rref methods had a
- # parameter indicating how many columns should be used for elimination.
- if len(pivots) != n or pivots and pivots[-1] >= n:
- raise DMNonInvertibleMatrixError("Non-unique solution.")
- xnum = Aaug_rref[:n, n:]
- xden = denom
- return xnum, xden
- def solve_den_charpoly(self, b, cp=None, check=True):
- """
- Solve matrix equation $Ax = b$ using the characteristic polynomial.
- This method solves the square matrix equation $Ax = b$ for $x$ using
- the characteristic polynomial without any division or fractions in the
- ground domain.
- Examples
- ========
- Solve a matrix equation over the integers:
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[ZZ(1), ZZ(2)], [ZZ(3), ZZ(4)]], ZZ)
- >>> b = DM([[ZZ(5)], [ZZ(6)]], ZZ)
- >>> xnum, detA = A.solve_den_charpoly(b)
- >>> detA
- -2
- >>> xnum
- DomainMatrix([[8], [-9]], (2, 1), ZZ)
- >>> A * xnum == detA * b
- True
- Parameters
- ==========
- self : DomainMatrix
- The ``n x n`` matrix `A` in the equation `Ax = b`. Must be square
- and invertible.
- b : DomainMatrix
- The ``n x m`` matrix `b` for the rhs.
- cp : list, optional
- The characteristic polynomial of the matrix `A` if known. If not
- given, it will be computed using :meth:`charpoly`.
- check : bool, optional
- If ``True`` (the default) check that the determinant is not zero
- and raise an error if it is. If ``False`` then if the determinant
- is zero the return value will be equal to ``(A.adjugate()*b, 0)``.
- Returns
- =======
- (xnum, detA) : (DomainMatrix, DomainElement)
- The solution of the equation `Ax = b` as a matrix numerator and
- scalar denominator pair. The denominator is equal to the
- determinant of `A` and the numerator is ``adj(A)*b``.
- The solution $x$ is given by ``x = xnum / detA``. The division free
- invariant is ``A * xnum == detA * b``.
- If ``b`` is the identity matrix, then ``xnum`` is the adjugate matrix
- and we have ``A * adj(A) == detA * I``.
- See Also
- ========
- solve_den
- Main frontend for solving matrix equations with denominator.
- solve_den_rref
- Solve matrix equations using fraction-free RREF.
- inv_den
- Invert a matrix using the characteristic polynomial.
- """
- A, b = self.unify(b)
- m, n = self.shape
- mb, nb = b.shape
- if m != n:
- raise DMNonSquareMatrixError("Matrix must be square")
- if mb != m:
- raise DMShapeError("Matrix and vector must have the same number of rows")
- f, detA = self.adj_poly_det(cp=cp)
- if check and not detA:
- raise DMNonInvertibleMatrixError("Matrix is not invertible")
- # Compute adj(A)*b = det(A)*inv(A)*b using Horner's method without
- # constructing inv(A) explicitly.
- adjA_b = self.eval_poly_mul(f, b)
- return (adjA_b, detA)
- def adj_poly_det(self, cp=None):
- """
- Return the polynomial $p$ such that $p(A) = adj(A)$ and also the
- determinant of $A$.
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], QQ)
- >>> p, detA = A.adj_poly_det()
- >>> p
- [-1, 5]
- >>> p_A = A.eval_poly(p)
- >>> p_A
- DomainMatrix([[4, -2], [-3, 1]], (2, 2), QQ)
- >>> p[0]*A**1 + p[1]*A**0 == p_A
- True
- >>> p_A == A.adjugate()
- True
- >>> A * A.adjugate() == detA * A.eye(A.shape, A.domain).to_dense()
- True
- See Also
- ========
- adjugate
- eval_poly
- adj_det
- """
- # Cayley-Hamilton says that a matrix satisfies its own minimal
- # polynomial
- #
- # p[0]*A^n + p[1]*A^(n-1) + ... + p[n]*I = 0
- #
- # with p[0]=1 and p[n]=(-1)^n*det(A) or
- #
- # det(A)*I = -(-1)^n*(p[0]*A^(n-1) + p[1]*A^(n-2) + ... + p[n-1]*A).
- #
- # Define a new polynomial f with f[i] = -(-1)^n*p[i] for i=0..n-1. Then
- #
- # det(A)*I = f[0]*A^n + f[1]*A^(n-1) + ... + f[n-1]*A.
- #
- # Multiplying on the right by inv(A) gives
- #
- # det(A)*inv(A) = f[0]*A^(n-1) + f[1]*A^(n-2) + ... + f[n-1].
- #
- # So adj(A) = det(A)*inv(A) = f(A)
- A = self
- m, n = self.shape
- if m != n:
- raise DMNonSquareMatrixError("Matrix must be square")
- if cp is None:
- cp = A.charpoly()
- if len(cp) % 2:
- # n is even
- detA = cp[-1]
- f = [-cpi for cpi in cp[:-1]]
- else:
- # n is odd
- detA = -cp[-1]
- f = cp[:-1]
- return f, detA
- def eval_poly(self, p):
- """
- Evaluate polynomial function of a matrix $p(A)$.
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], QQ)
- >>> p = [QQ(1), QQ(2), QQ(3)]
- >>> p_A = A.eval_poly(p)
- >>> p_A
- DomainMatrix([[12, 14], [21, 33]], (2, 2), QQ)
- >>> p_A == p[0]*A**2 + p[1]*A + p[2]*A**0
- True
- See Also
- ========
- eval_poly_mul
- """
- A = self
- m, n = A.shape
- if m != n:
- raise DMNonSquareMatrixError("Matrix must be square")
- if not p:
- return self.zeros(self.shape, self.domain)
- elif len(p) == 1:
- return p[0] * self.eye(self.shape, self.domain)
- # Evaluate p(A) using Horner's method:
- # XXX: Use Paterson-Stockmeyer method?
- I = A.eye(A.shape, A.domain)
- p_A = p[0] * I
- for pi in p[1:]:
- p_A = A*p_A + pi*I
- return p_A
- def eval_poly_mul(self, p, B):
- r"""
- Evaluate polynomial matrix product $p(A) \times B$.
- Evaluate the polynomial matrix product $p(A) \times B$ using Horner's
- method without creating the matrix $p(A)$ explicitly. If $B$ is a
- column matrix then this method will only use matrix-vector multiplies
- and no matrix-matrix multiplies are needed.
- If $B$ is square or wide or if $A$ can be represented in a simpler
- domain than $B$ then it might be faster to evaluate $p(A)$ explicitly
- (see :func:`eval_poly`) and then multiply with $B$.
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DM
- >>> A = DM([[QQ(1), QQ(2)], [QQ(3), QQ(4)]], QQ)
- >>> b = DM([[QQ(5)], [QQ(6)]], QQ)
- >>> p = [QQ(1), QQ(2), QQ(3)]
- >>> p_A_b = A.eval_poly_mul(p, b)
- >>> p_A_b
- DomainMatrix([[144], [303]], (2, 1), QQ)
- >>> p_A_b == p[0]*A**2*b + p[1]*A*b + p[2]*b
- True
- >>> A.eval_poly_mul(p, b) == A.eval_poly(p)*b
- True
- See Also
- ========
- eval_poly
- solve_den_charpoly
- """
- A = self
- m, n = A.shape
- mb, nb = B.shape
- if m != n:
- raise DMNonSquareMatrixError("Matrix must be square")
- if mb != n:
- raise DMShapeError("Matrices are not aligned")
- if A.domain != B.domain:
- raise DMDomainError("Matrices must have the same domain")
- # Given a polynomial p(x) = p[0]*x^n + p[1]*x^(n-1) + ... + p[n-1]
- # and matrices A and B we want to find
- #
- # p(A)*B = p[0]*A^n*B + p[1]*A^(n-1)*B + ... + p[n-1]*B
- #
- # Factoring out A term by term we get
- #
- # p(A)*B = A*(...A*(A*(A*(p[0]*B) + p[1]*B) + p[2]*B) + ...) + p[n-1]*B
- #
- # where each pair of brackets represents one iteration of the loop
- # below starting from the innermost p[0]*B. If B is a column matrix
- # then products like A*(...) are matrix-vector multiplies and products
- # like p[i]*B are scalar-vector multiplies so there are no
- # matrix-matrix multiplies.
- if not p:
- return B.zeros(B.shape, B.domain, fmt=B.rep.fmt)
- p_A_B = p[0]*B
- for p_i in p[1:]:
- p_A_B = A*p_A_B + p_i*B
- return p_A_B
- def lu(self):
- r"""
- Returns Lower and Upper decomposition of the DomainMatrix
- Returns
- =======
- (L, U, exchange)
- L, U are Lower and Upper decomposition of the DomainMatrix,
- exchange is the list of indices of rows exchanged in the
- decomposition.
- Raises
- ======
- ValueError
- If the domain of DomainMatrix not a Field
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [QQ(1), QQ(-1)],
- ... [QQ(2), QQ(-2)]], (2, 2), QQ)
- >>> L, U, exchange = A.lu()
- >>> L
- DomainMatrix([[1, 0], [2, 1]], (2, 2), QQ)
- >>> U
- DomainMatrix([[1, -1], [0, 0]], (2, 2), QQ)
- >>> exchange
- []
- See Also
- ========
- lu_solve
- """
- if not self.domain.is_Field:
- raise DMNotAField('Not a field')
- L, U, swaps = self.rep.lu()
- return self.from_rep(L), self.from_rep(U), swaps
- def qr(self):
- r"""
- QR decomposition of the DomainMatrix.
- Explanation
- ===========
- The QR decomposition expresses a matrix as the product of an orthogonal
- matrix (Q) and an upper triangular matrix (R). In this implementation,
- Q is not orthonormal: its columns are orthogonal but not normalized to
- unit vectors. This avoids unnecessary divisions and is particularly
- suited for exact arithmetic domains.
- Note
- ====
- This implementation is valid only for matrices over real domains. For
- matrices over complex domains, a proper QR decomposition would require
- handling conjugation to ensure orthogonality.
- Returns
- =======
- (Q, R)
- Q is the orthogonal matrix, and R is the upper triangular matrix
- resulting from the QR decomposition of the DomainMatrix.
- Raises
- ======
- DMDomainError
- If the domain of the DomainMatrix is not a field (e.g., QQ).
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([[1, 2], [3, 4], [5, 6]], (3, 2), QQ)
- >>> Q, R = A.qr()
- >>> Q
- DomainMatrix([[1, 26/35], [3, 8/35], [5, -2/7]], (3, 2), QQ)
- >>> R
- DomainMatrix([[1, 44/35], [0, 1]], (2, 2), QQ)
- >>> Q * R == A
- True
- >>> (Q.transpose() * Q).is_diagonal
- True
- >>> R.is_upper
- True
- See Also
- ========
- lu
- """
- ddm_q, ddm_r = self.rep.qr()
- Q = self.from_rep(ddm_q)
- R = self.from_rep(ddm_r)
- return Q, R
- def lu_solve(self, rhs):
- r"""
- Solver for DomainMatrix x in the A*x = B
- Parameters
- ==========
- rhs : DomainMatrix B
- Returns
- =======
- DomainMatrix
- x in A*x = B
- Raises
- ======
- DMShapeError
- If the DomainMatrix A and rhs have different number of rows
- ValueError
- If the domain of DomainMatrix A not a Field
- Examples
- ========
- >>> from sympy import QQ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [QQ(1), QQ(2)],
- ... [QQ(3), QQ(4)]], (2, 2), QQ)
- >>> B = DomainMatrix([
- ... [QQ(1), QQ(1)],
- ... [QQ(0), QQ(1)]], (2, 2), QQ)
- >>> A.lu_solve(B)
- DomainMatrix([[-2, -1], [3/2, 1]], (2, 2), QQ)
- See Also
- ========
- lu
- """
- if self.shape[0] != rhs.shape[0]:
- raise DMShapeError("Shape")
- if not self.domain.is_Field:
- raise DMNotAField('Not a field')
- sol = self.rep.lu_solve(rhs.rep)
- return self.from_rep(sol)
- def fflu(self):
- """
- Fraction-free LU decomposition of DomainMatrix.
- Explanation
- ===========
- This method computes the PLDU decomposition
- using Gauss-Bareiss elimination in a fraction-free manner,
- it ensures that all intermediate results remain in
- the domain of the input matrix. Unlike standard
- LU decomposition, which introduces division, this approach
- avoids fractions, making it particularly suitable
- for exact arithmetic over integers or polynomials.
- This method satisfies the invariant:
- P * A = L * inv(D) * U
- Returns
- =======
- (P, L, D, U)
- - P (Permutation matrix)
- - L (Lower triangular matrix)
- - D (Diagonal matrix)
- - U (Upper triangular matrix)
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([[1, 2], [3, 4]], (2, 2), ZZ)
- >>> P, L, D, U = A.fflu()
- >>> P
- DomainMatrix([[1, 0], [0, 1]], (2, 2), ZZ)
- >>> L
- DomainMatrix([[1, 0], [3, -2]], (2, 2), ZZ)
- >>> D
- DomainMatrix([[1, 0], [0, -2]], (2, 2), ZZ)
- >>> U
- DomainMatrix([[1, 2], [0, -2]], (2, 2), ZZ)
- >>> L.is_lower and U.is_upper and D.is_diagonal
- True
- >>> L * D.to_field().inv() * U == P * A.to_field()
- True
- >>> I, d = D.inv_den()
- >>> L * I * U == d * P * A
- True
- See Also
- ========
- sympy.polys.matrices.ddm.DDM.fflu
- References
- ==========
- .. [1] Nakos, G. C., Turner, P. R., & Williams, R. M. (1997). Fraction-free
- algorithms for linear and polynomial equations. ACM SIGSAM Bulletin,
- 31(3), 11-19. https://doi.org/10.1145/271130.271133
- .. [2] Middeke, J.; Jeffrey, D.J.; Koutschan, C. (2020), "Common Factors
- in Fraction-Free Matrix Decompositions", Mathematics in Computer Science,
- 15 (4): 589–608, arXiv:2005.12380, doi:10.1007/s11786-020-00495-9
- .. [3] https://en.wikipedia.org/wiki/Bareiss_algorithm
- """
- from_rep = self.from_rep
- P, L, D, U = self.rep.fflu()
- return from_rep(P), from_rep(L), from_rep(D), from_rep(U)
- def _solve(A, b):
- # XXX: Not sure about this method or its signature. It is just created
- # because it is needed by the holonomic module.
- if A.shape[0] != b.shape[0]:
- raise DMShapeError("Shape")
- if A.domain != b.domain or not A.domain.is_Field:
- raise DMNotAField('Not a field')
- Aaug = A.hstack(b)
- Arref, pivots = Aaug.rref()
- particular = Arref.from_rep(Arref.rep.particular())
- nullspace_rep, nonpivots = Arref[:,:-1].rep.nullspace()
- nullspace = Arref.from_rep(nullspace_rep)
- return particular, nullspace
- def charpoly(self):
- r"""
- Characteristic polynomial of a square matrix.
- Computes the characteristic polynomial in a fully expanded form using
- division free arithmetic. If a factorization of the characteristic
- polynomial is needed then it is more efficient to call
- :meth:`charpoly_factor_list` than calling :meth:`charpoly` and then
- factorizing the result.
- Returns
- =======
- list: list of DomainElement
- coefficients of the characteristic polynomial
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> A.charpoly()
- [1, -5, -2]
- See Also
- ========
- charpoly_factor_list
- Compute the factorisation of the characteristic polynomial.
- charpoly_factor_blocks
- A partial factorisation of the characteristic polynomial that can
- be computed more efficiently than either the full factorisation or
- the fully expanded polynomial.
- """
- M = self
- K = M.domain
- factors = M.charpoly_factor_blocks()
- cp = [K.one]
- for f, mult in factors:
- for _ in range(mult):
- cp = dup_mul(cp, f, K)
- return cp
- def charpoly_factor_list(self):
- """
- Full factorization of the characteristic polynomial.
- Examples
- ========
- >>> from sympy.polys.matrices import DM
- >>> from sympy import ZZ
- >>> M = DM([[6, -1, 0, 0],
- ... [9, 12, 0, 0],
- ... [0, 0, 1, 2],
- ... [0, 0, 5, 6]], ZZ)
- Compute the factorization of the characteristic polynomial:
- >>> M.charpoly_factor_list()
- [([1, -9], 2), ([1, -7, -4], 1)]
- Use :meth:`charpoly` to get the unfactorized characteristic polynomial:
- >>> M.charpoly()
- [1, -25, 203, -495, -324]
- The same calculations with ``Matrix``:
- >>> M.to_Matrix().charpoly().as_expr()
- lambda**4 - 25*lambda**3 + 203*lambda**2 - 495*lambda - 324
- >>> M.to_Matrix().charpoly().as_expr().factor()
- (lambda - 9)**2*(lambda**2 - 7*lambda - 4)
- Returns
- =======
- list: list of pairs (factor, multiplicity)
- A full factorization of the characteristic polynomial.
- See Also
- ========
- charpoly
- Expanded form of the characteristic polynomial.
- charpoly_factor_blocks
- A partial factorisation of the characteristic polynomial that can
- be computed more efficiently.
- """
- M = self
- K = M.domain
- # It is more efficient to start from the partial factorization provided
- # for free by M.charpoly_factor_blocks than the expanded M.charpoly.
- factors = M.charpoly_factor_blocks()
- factors_irreducible = []
- for factor_i, mult_i in factors:
- _, factors_list = dup_factor_list(factor_i, K)
- for factor_j, mult_j in factors_list:
- factors_irreducible.append((factor_j, mult_i * mult_j))
- return _collect_factors(factors_irreducible)
- def charpoly_factor_blocks(self):
- """
- Partial factorisation of the characteristic polynomial.
- This factorisation arises from a block structure of the matrix (if any)
- and so the factors are not guaranteed to be irreducible. The
- :meth:`charpoly_factor_blocks` method is the most efficient way to get
- a representation of the characteristic polynomial but the result is
- neither fully expanded nor fully factored.
- Examples
- ========
- >>> from sympy.polys.matrices import DM
- >>> from sympy import ZZ
- >>> M = DM([[6, -1, 0, 0],
- ... [9, 12, 0, 0],
- ... [0, 0, 1, 2],
- ... [0, 0, 5, 6]], ZZ)
- This computes a partial factorization using only the block structure of
- the matrix to reveal factors:
- >>> M.charpoly_factor_blocks()
- [([1, -18, 81], 1), ([1, -7, -4], 1)]
- These factors correspond to the two diagonal blocks in the matrix:
- >>> DM([[6, -1], [9, 12]], ZZ).charpoly()
- [1, -18, 81]
- >>> DM([[1, 2], [5, 6]], ZZ).charpoly()
- [1, -7, -4]
- Use :meth:`charpoly_factor_list` to get a complete factorization into
- irreducibles:
- >>> M.charpoly_factor_list()
- [([1, -9], 2), ([1, -7, -4], 1)]
- Use :meth:`charpoly` to get the expanded characteristic polynomial:
- >>> M.charpoly()
- [1, -25, 203, -495, -324]
- Returns
- =======
- list: list of pairs (factor, multiplicity)
- A partial factorization of the characteristic polynomial.
- See Also
- ========
- charpoly
- Compute the fully expanded characteristic polynomial.
- charpoly_factor_list
- Compute a full factorization of the characteristic polynomial.
- """
- M = self
- if not M.is_square:
- raise DMNonSquareMatrixError("not square")
- # scc returns indices that permute the matrix into block triangular
- # form and can extract the diagonal blocks. M.charpoly() is equal to
- # the product of the diagonal block charpolys.
- components = M.scc()
- block_factors = []
- for indices in components:
- block = M.extract(indices, indices)
- block_factors.append((block.charpoly_base(), 1))
- return _collect_factors(block_factors)
- def charpoly_base(self):
- """
- Base case for :meth:`charpoly_factor_blocks` after block decomposition.
- This method is used internally by :meth:`charpoly_factor_blocks` as the
- base case for computing the characteristic polynomial of a block. It is
- more efficient to call :meth:`charpoly_factor_blocks`, :meth:`charpoly`
- or :meth:`charpoly_factor_list` rather than call this method directly.
- This will use either the dense or the sparse implementation depending
- on the sparsity of the matrix and will clear denominators if possible
- before calling :meth:`charpoly_berk` to compute the characteristic
- polynomial using the Berkowitz algorithm.
- See Also
- ========
- charpoly
- charpoly_factor_list
- charpoly_factor_blocks
- charpoly_berk
- """
- M = self
- K = M.domain
- # It seems that the sparse implementation is always faster for random
- # matrices with fewer than 50% non-zero entries. This does not seem to
- # depend on domain, size, bit count etc.
- density = self.nnz() / self.shape[0]**2
- if density < 0.5:
- M = M.to_sparse()
- else:
- M = M.to_dense()
- # Clearing denominators is always more efficient if it can be done.
- # Doing it here after block decomposition is good because each block
- # might have a smaller denominator. However it might be better for
- # charpoly and charpoly_factor_list to restore the denominators only at
- # the very end so that they can call e.g. dup_factor_list before
- # restoring the denominators. The methods would need to be changed to
- # return (poly, denom) pairs to make that work though.
- clear_denoms = K.is_Field and K.has_assoc_Ring
- if clear_denoms:
- clear_denoms = True
- d, M = M.clear_denoms(convert=True)
- d = d.element
- K_f = K
- K_r = M.domain
- # Berkowitz algorithm over K_r.
- cp = M.charpoly_berk()
- if clear_denoms:
- # Restore the denominator in the charpoly over K_f.
- #
- # If M = N/d then p_M(x) = p_N(x*d)/d^n.
- cp = dup_convert(cp, K_r, K_f)
- p = [K_f.one, K_f.zero]
- q = [K_f.one/d]
- cp = dup_transform(cp, p, q, K_f)
- return cp
- def charpoly_berk(self):
- """Compute the characteristic polynomial using the Berkowitz algorithm.
- This method directly calls the underlying implementation of the
- Berkowitz algorithm (:meth:`sympy.polys.matrices.dense.ddm_berk` or
- :meth:`sympy.polys.matrices.sdm.sdm_berk`).
- This is used by :meth:`charpoly` and other methods as the base case for
- for computing the characteristic polynomial. However those methods will
- apply other optimizations such as block decomposition, clearing
- denominators and converting between dense and sparse representations
- before calling this method. It is more efficient to call those methods
- instead of this one but this method is provided for direct access to
- the Berkowitz algorithm.
- Examples
- ========
- >>> from sympy.polys.matrices import DM
- >>> from sympy import QQ
- >>> M = DM([[6, -1, 0, 0],
- ... [9, 12, 0, 0],
- ... [0, 0, 1, 2],
- ... [0, 0, 5, 6]], QQ)
- >>> M.charpoly_berk()
- [1, -25, 203, -495, -324]
- See Also
- ========
- charpoly
- charpoly_base
- charpoly_factor_list
- charpoly_factor_blocks
- sympy.polys.matrices.dense.ddm_berk
- sympy.polys.matrices.sdm.sdm_berk
- """
- return self.rep.charpoly()
- @classmethod
- def eye(cls, shape, domain):
- r"""
- Return identity matrix of size n or shape (m, n).
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import QQ
- >>> DomainMatrix.eye(3, QQ)
- DomainMatrix({0: {0: 1}, 1: {1: 1}, 2: {2: 1}}, (3, 3), QQ)
- """
- if isinstance(shape, int):
- shape = (shape, shape)
- return cls.from_rep(SDM.eye(shape, domain))
- @classmethod
- def diag(cls, diagonal, domain, shape=None):
- r"""
- Return diagonal matrix with entries from ``diagonal``.
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import ZZ
- >>> DomainMatrix.diag([ZZ(5), ZZ(6)], ZZ)
- DomainMatrix({0: {0: 5}, 1: {1: 6}}, (2, 2), ZZ)
- """
- if shape is None:
- N = len(diagonal)
- shape = (N, N)
- return cls.from_rep(SDM.diag(diagonal, domain, shape))
- @classmethod
- def zeros(cls, shape, domain, *, fmt='sparse'):
- """Returns a zero DomainMatrix of size shape, belonging to the specified domain
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import QQ
- >>> DomainMatrix.zeros((2, 3), QQ)
- DomainMatrix({}, (2, 3), QQ)
- """
- return cls.from_rep(SDM.zeros(shape, domain))
- @classmethod
- def ones(cls, shape, domain):
- """Returns a DomainMatrix of 1s, of size shape, belonging to the specified domain
- Examples
- ========
- >>> from sympy.polys.matrices import DomainMatrix
- >>> from sympy import QQ
- >>> DomainMatrix.ones((2,3), QQ)
- DomainMatrix([[1, 1, 1], [1, 1, 1]], (2, 3), QQ)
- """
- return cls.from_rep(DDM.ones(shape, domain).to_dfm_or_ddm())
- def __eq__(A, B):
- r"""
- Checks for two DomainMatrix matrices to be equal or not
- Parameters
- ==========
- A, B: DomainMatrix
- to check equality
- Returns
- =======
- Boolean
- True for equal, else False
- Raises
- ======
- NotImplementedError
- If B is not a DomainMatrix
- Examples
- ========
- >>> from sympy import ZZ
- >>> from sympy.polys.matrices import DomainMatrix
- >>> A = DomainMatrix([
- ... [ZZ(1), ZZ(2)],
- ... [ZZ(3), ZZ(4)]], (2, 2), ZZ)
- >>> B = DomainMatrix([
- ... [ZZ(1), ZZ(1)],
- ... [ZZ(0), ZZ(1)]], (2, 2), ZZ)
- >>> A.__eq__(A)
- True
- >>> A.__eq__(B)
- False
- """
- if not isinstance(A, type(B)):
- return NotImplemented
- return A.domain == B.domain and A.rep == B.rep
- def unify_eq(A, B):
- if A.shape != B.shape:
- return False
- if A.domain != B.domain:
- A, B = A.unify(B)
- return A == B
- def lll(A, delta=QQ(3, 4)):
- """
- Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm.
- See [1]_ and [2]_.
- Parameters
- ==========
- delta : QQ, optional
- The Lovász parameter. Must be in the interval (0.25, 1), with larger
- values producing a more reduced basis. The default is 0.75 for
- historical reasons.
- Returns
- =======
- The reduced basis as a DomainMatrix over ZZ.
- Throws
- ======
- DMValueError: if delta is not in the range (0.25, 1)
- DMShapeError: if the matrix is not of shape (m, n) with m <= n
- DMDomainError: if the matrix domain is not ZZ
- DMRankError: if the matrix contains linearly dependent rows
- Examples
- ========
- >>> from sympy.polys.domains import ZZ, QQ
- >>> from sympy.polys.matrices import DM
- >>> x = DM([[1, 0, 0, 0, -20160],
- ... [0, 1, 0, 0, 33768],
- ... [0, 0, 1, 0, 39578],
- ... [0, 0, 0, 1, 47757]], ZZ)
- >>> y = DM([[10, -3, -2, 8, -4],
- ... [3, -9, 8, 1, -11],
- ... [-3, 13, -9, -3, -9],
- ... [-12, -7, -11, 9, -1]], ZZ)
- >>> assert x.lll(delta=QQ(5, 6)) == y
- Notes
- =====
- The implementation is derived from the Maple code given in Figures 4.3
- and 4.4 of [3]_ (pp.68-69). It uses the efficient method of only calculating
- state updates as they are required.
- See also
- ========
- lll_transform
- References
- ==========
- .. [1] https://en.wikipedia.org/wiki/Lenstra%E2%80%93Lenstra%E2%80%93Lov%C3%A1sz_lattice_basis_reduction_algorithm
- .. [2] https://web.archive.org/web/20221029115428/https://web.cs.elte.hu/~lovasz/scans/lll.pdf
- .. [3] Murray R. Bremner, "Lattice Basis Reduction: An Introduction to the LLL Algorithm and Its Applications"
- """
- return DomainMatrix.from_rep(A.rep.lll(delta=delta))
- def lll_transform(A, delta=QQ(3, 4)):
- """
- Performs the Lenstra–Lenstra–Lovász (LLL) basis reduction algorithm
- and returns the reduced basis and transformation matrix.
- Explanation
- ===========
- Parameters, algorithm and basis are the same as for :meth:`lll` except that
- the return value is a tuple `(B, T)` with `B` the reduced basis and
- `T` a transformation matrix. The original basis `A` is transformed to
- `B` with `T*A == B`. If only `B` is needed then :meth:`lll` should be
- used as it is a little faster.
- Examples
- ========
- >>> from sympy.polys.domains import ZZ, QQ
- >>> from sympy.polys.matrices import DM
- >>> X = DM([[1, 0, 0, 0, -20160],
- ... [0, 1, 0, 0, 33768],
- ... [0, 0, 1, 0, 39578],
- ... [0, 0, 0, 1, 47757]], ZZ)
- >>> B, T = X.lll_transform(delta=QQ(5, 6))
- >>> T * X == B
- True
- See also
- ========
- lll
- """
- reduced, transform = A.rep.lll_transform(delta=delta)
- return DomainMatrix.from_rep(reduced), DomainMatrix.from_rep(transform)
- def _collect_factors(factors_list):
- """
- Collect repeating factors and sort.
- >>> from sympy.polys.matrices.domainmatrix import _collect_factors
- >>> _collect_factors([([1, 2], 2), ([1, 4], 3), ([1, 2], 5)])
- [([1, 4], 3), ([1, 2], 7)]
- """
- factors = Counter()
- for factor, exponent in factors_list:
- factors[tuple(factor)] += exponent
- factors_list = [(list(f), e) for f, e in factors.items()]
- return _sort_factors(factors_list)
|