12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558 |
- """
- This module contains functions for the computation
- of Euclidean, (generalized) Sturmian, (modified) subresultant
- polynomial remainder sequences (prs's) of two polynomials;
- included are also three functions for the computation of the
- resultant of two polynomials.
- Except for the function res_z(), which computes the resultant
- of two polynomials, the pseudo-remainder function prem()
- of sympy is _not_ used by any of the functions in the module.
- Instead of prem() we use the function
- rem_z().
- Included is also the function quo_z().
- An explanation of why we avoid prem() can be found in the
- references stated in the docstring of rem_z().
- 1. Theoretical background:
- ==========================
- Consider the polynomials f, g in Z[x] of degrees deg(f) = n and
- deg(g) = m with n >= m.
- Definition 1:
- =============
- The sign sequence of a polynomial remainder sequence (prs) is the
- sequence of signs of the leading coefficients of its polynomials.
- Sign sequences can be computed with the function:
- sign_seq(poly_seq, x)
- Definition 2:
- =============
- A polynomial remainder sequence (prs) is called complete if the
- degree difference between any two consecutive polynomials is 1;
- otherwise, it called incomplete.
- It is understood that f, g belong to the sequences mentioned in
- the two definitions above.
- 1A. Euclidean and subresultant prs's:
- =====================================
- The subresultant prs of f, g is a sequence of polynomials in Z[x]
- analogous to the Euclidean prs, the sequence obtained by applying
- on f, g Euclid's algorithm for polynomial greatest common divisors
- (gcd) in Q[x].
- The subresultant prs differs from the Euclidean prs in that the
- coefficients of each polynomial in the former sequence are determinants
- --- also referred to as subresultants --- of appropriately selected
- sub-matrices of sylvester1(f, g, x), Sylvester's matrix of 1840 of
- dimensions (n + m) * (n + m).
- Recall that the determinant of sylvester1(f, g, x) itself is
- called the resultant of f, g and serves as a criterion of whether
- the two polynomials have common roots or not.
- In SymPy the resultant is computed with the function
- resultant(f, g, x). This function does _not_ evaluate the
- determinant of sylvester(f, g, x, 1); instead, it returns
- the last member of the subresultant prs of f, g, multiplied
- (if needed) by an appropriate power of -1; see the caveat below.
- In this module we use three functions to compute the
- resultant of f, g:
- a) res(f, g, x) computes the resultant by evaluating
- the determinant of sylvester(f, g, x, 1);
- b) res_q(f, g, x) computes the resultant recursively, by
- performing polynomial divisions in Q[x] with the function rem();
- c) res_z(f, g, x) computes the resultant recursively, by
- performing polynomial divisions in Z[x] with the function prem().
- Caveat: If Df = degree(f, x) and Dg = degree(g, x), then:
- resultant(f, g, x) = (-1)**(Df*Dg) * resultant(g, f, x).
- For complete prs's the sign sequence of the Euclidean prs of f, g
- is identical to the sign sequence of the subresultant prs of f, g
- and the coefficients of one sequence are easily computed from the
- coefficients of the other.
- For incomplete prs's the polynomials in the subresultant prs, generally
- differ in sign from those of the Euclidean prs, and --- unlike the
- case of complete prs's --- it is not at all obvious how to compute
- the coefficients of one sequence from the coefficients of the other.
- 1B. Sturmian and modified subresultant prs's:
- =============================================
- For the same polynomials f, g in Z[x] mentioned above, their ``modified''
- subresultant prs is a sequence of polynomials similar to the Sturmian
- prs, the sequence obtained by applying in Q[x] Sturm's algorithm on f, g.
- The two sequences differ in that the coefficients of each polynomial
- in the modified subresultant prs are the determinants --- also referred
- to as modified subresultants --- of appropriately selected sub-matrices
- of sylvester2(f, g, x), Sylvester's matrix of 1853 of dimensions 2n x 2n.
- The determinant of sylvester2 itself is called the modified resultant
- of f, g and it also can serve as a criterion of whether the two
- polynomials have common roots or not.
- For complete prs's the sign sequence of the Sturmian prs of f, g is
- identical to the sign sequence of the modified subresultant prs of
- f, g and the coefficients of one sequence are easily computed from
- the coefficients of the other.
- For incomplete prs's the polynomials in the modified subresultant prs,
- generally differ in sign from those of the Sturmian prs, and --- unlike
- the case of complete prs's --- it is not at all obvious how to compute
- the coefficients of one sequence from the coefficients of the other.
- As Sylvester pointed out, the coefficients of the polynomial remainders
- obtained as (modified) subresultants are the smallest possible without
- introducing rationals and without computing (integer) greatest common
- divisors.
- 1C. On terminology:
- ===================
- Whence the terminology? Well generalized Sturmian prs's are
- ``modifications'' of Euclidean prs's; the hint came from the title
- of the Pell-Gordon paper of 1917.
- In the literature one also encounters the name ``non signed'' and
- ``signed'' prs for Euclidean and Sturmian prs respectively.
- Likewise ``non signed'' and ``signed'' subresultant prs for
- subresultant and modified subresultant prs respectively.
- 2. Functions in the module:
- ===========================
- No function utilizes SymPy's function prem().
- 2A. Matrices:
- =============
- The functions sylvester(f, g, x, method=1) and
- sylvester(f, g, x, method=2) compute either Sylvester matrix.
- They can be used to compute (modified) subresultant prs's by
- direct determinant evaluation.
- The function bezout(f, g, x, method='prs') provides a matrix of
- smaller dimensions than either Sylvester matrix. It is the function
- of choice for computing (modified) subresultant prs's by direct
- determinant evaluation.
- sylvester(f, g, x, method=1)
- sylvester(f, g, x, method=2)
- bezout(f, g, x, method='prs')
- The following identity holds:
- bezout(f, g, x, method='prs') =
- backward_eye(deg(f))*bezout(f, g, x, method='bz')*backward_eye(deg(f))
- 2B. Subresultant and modified subresultant prs's by
- ===================================================
- determinant evaluations:
- =======================
- We use the Sylvester matrices of 1840 and 1853 to
- compute, respectively, subresultant and modified
- subresultant polynomial remainder sequences. However,
- for large matrices this approach takes a lot of time.
- Instead of utilizing the Sylvester matrices, we can
- employ the Bezout matrix which is of smaller dimensions.
- subresultants_sylv(f, g, x)
- modified_subresultants_sylv(f, g, x)
- subresultants_bezout(f, g, x)
- modified_subresultants_bezout(f, g, x)
- 2C. Subresultant prs's by ONE determinant evaluation:
- =====================================================
- All three functions in this section evaluate one determinant
- per remainder polynomial; this is the determinant of an
- appropriately selected sub-matrix of sylvester1(f, g, x),
- Sylvester's matrix of 1840.
- To compute the remainder polynomials the function
- subresultants_rem(f, g, x) employs rem(f, g, x).
- By contrast, the other two functions implement Van Vleck's ideas
- of 1900 and compute the remainder polynomials by trinagularizing
- sylvester2(f, g, x), Sylvester's matrix of 1853.
- subresultants_rem(f, g, x)
- subresultants_vv(f, g, x)
- subresultants_vv_2(f, g, x).
- 2E. Euclidean, Sturmian prs's in Q[x]:
- ======================================
- euclid_q(f, g, x)
- sturm_q(f, g, x)
- 2F. Euclidean, Sturmian and (modified) subresultant prs's P-G:
- ==============================================================
- All functions in this section are based on the Pell-Gordon (P-G)
- theorem of 1917.
- Computations are done in Q[x], employing the function rem(f, g, x)
- for the computation of the remainder polynomials.
- euclid_pg(f, g, x)
- sturm pg(f, g, x)
- subresultants_pg(f, g, x)
- modified_subresultants_pg(f, g, x)
- 2G. Euclidean, Sturmian and (modified) subresultant prs's A-M-V:
- ================================================================
- All functions in this section are based on the Akritas-Malaschonok-
- Vigklas (A-M-V) theorem of 2015.
- Computations are done in Z[x], employing the function rem_z(f, g, x)
- for the computation of the remainder polynomials.
- euclid_amv(f, g, x)
- sturm_amv(f, g, x)
- subresultants_amv(f, g, x)
- modified_subresultants_amv(f, g, x)
- 2Ga. Exception:
- ===============
- subresultants_amv_q(f, g, x)
- This function employs rem(f, g, x) for the computation of
- the remainder polynomials, despite the fact that it implements
- the A-M-V Theorem.
- It is included in our module in order to show that theorems P-G
- and A-M-V can be implemented utilizing either the function
- rem(f, g, x) or the function rem_z(f, g, x).
- For clearly historical reasons --- since the Collins-Brown-Traub
- coefficients-reduction factor beta_i was not available in 1917 ---
- we have implemented the Pell-Gordon theorem with the function
- rem(f, g, x) and the A-M-V Theorem with the function rem_z(f, g, x).
- 2H. Resultants:
- ===============
- res(f, g, x)
- res_q(f, g, x)
- res_z(f, g, x)
- """
- from sympy.concrete.summations import summation
- from sympy.core.function import expand
- from sympy.core.numbers import nan
- from sympy.core.singleton import S
- from sympy.core.symbol import Dummy as var
- from sympy.functions.elementary.complexes import Abs, sign
- from sympy.functions.elementary.integers import floor
- from sympy.matrices.dense import eye, Matrix, zeros
- from sympy.printing.pretty.pretty import pretty_print as pprint
- from sympy.simplify.simplify import simplify
- from sympy.polys.domains import QQ
- from sympy.polys.polytools import degree, LC, Poly, pquo, quo, prem, rem
- from sympy.polys.polyerrors import PolynomialError
- def sylvester(f, g, x, method = 1):
- '''
- The input polynomials f, g are in Z[x] or in Q[x]. Let m = degree(f, x),
- n = degree(g, x) and mx = max(m, n).
- a. If method = 1 (default), computes sylvester1, Sylvester's matrix of 1840
- of dimension (m + n) x (m + n). The determinants of properly chosen
- submatrices of this matrix (a.k.a. subresultants) can be
- used to compute the coefficients of the Euclidean PRS of f, g.
- b. If method = 2, computes sylvester2, Sylvester's matrix of 1853
- of dimension (2*mx) x (2*mx). The determinants of properly chosen
- submatrices of this matrix (a.k.a. ``modified'' subresultants) can be
- used to compute the coefficients of the Sturmian PRS of f, g.
- Applications of these Matrices can be found in the references below.
- Especially, for applications of sylvester2, see the first reference!!
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem
- by Van Vleck Regarding Sturm Sequences. Serdica Journal of Computing,
- Vol. 7, No 4, 101-134, 2013.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
- and Modified Subresultant Polynomial Remainder Sequences.''
- Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
- '''
- # obtain degrees of polys
- m, n = degree( Poly(f, x), x), degree( Poly(g, x), x)
- # Special cases:
- # A:: case m = n < 0 (i.e. both polys are 0)
- if m == n and n < 0:
- return Matrix([])
- # B:: case m = n = 0 (i.e. both polys are constants)
- if m == n and n == 0:
- return Matrix([])
- # C:: m == 0 and n < 0 or m < 0 and n == 0
- # (i.e. one poly is constant and the other is 0)
- if m == 0 and n < 0:
- return Matrix([])
- elif m < 0 and n == 0:
- return Matrix([])
- # D:: m >= 1 and n < 0 or m < 0 and n >=1
- # (i.e. one poly is of degree >=1 and the other is 0)
- if m >= 1 and n < 0:
- return Matrix([0])
- elif m < 0 and n >= 1:
- return Matrix([0])
- fp = Poly(f, x).all_coeffs()
- gp = Poly(g, x).all_coeffs()
- # Sylvester's matrix of 1840 (default; a.k.a. sylvester1)
- if method <= 1:
- M = zeros(m + n)
- k = 0
- for i in range(n):
- j = k
- for coeff in fp:
- M[i, j] = coeff
- j = j + 1
- k = k + 1
- k = 0
- for i in range(n, m + n):
- j = k
- for coeff in gp:
- M[i, j] = coeff
- j = j + 1
- k = k + 1
- return M
- # Sylvester's matrix of 1853 (a.k.a sylvester2)
- if method >= 2:
- if len(fp) < len(gp):
- h = []
- for i in range(len(gp) - len(fp)):
- h.append(0)
- fp[ : 0] = h
- else:
- h = []
- for i in range(len(fp) - len(gp)):
- h.append(0)
- gp[ : 0] = h
- mx = max(m, n)
- dim = 2*mx
- M = zeros( dim )
- k = 0
- for i in range( mx ):
- j = k
- for coeff in fp:
- M[2*i, j] = coeff
- j = j + 1
- j = k
- for coeff in gp:
- M[2*i + 1, j] = coeff
- j = j + 1
- k = k + 1
- return M
- def process_matrix_output(poly_seq, x):
- """
- poly_seq is a polynomial remainder sequence computed either by
- (modified_)subresultants_bezout or by (modified_)subresultants_sylv.
- This function removes from poly_seq all zero polynomials as well
- as all those whose degree is equal to the degree of a preceding
- polynomial in poly_seq, as we scan it from left to right.
- """
- L = poly_seq[:] # get a copy of the input sequence
- d = degree(L[1], x)
- i = 2
- while i < len(L):
- d_i = degree(L[i], x)
- if d_i < 0: # zero poly
- L.remove(L[i])
- i = i - 1
- if d == d_i: # poly degree equals degree of previous poly
- L.remove(L[i])
- i = i - 1
- if d_i >= 0:
- d = d_i
- i = i + 1
- return L
- def subresultants_sylv(f, g, x):
- """
- The input polynomials f, g are in Z[x] or in Q[x]. It is assumed
- that deg(f) >= deg(g).
- Computes the subresultant polynomial remainder sequence (prs)
- of f, g by evaluating determinants of appropriately selected
- submatrices of sylvester(f, g, x, 1). The dimensions of the
- latter are (deg(f) + deg(g)) x (deg(f) + deg(g)).
- Each coefficient is computed by evaluating the determinant of the
- corresponding submatrix of sylvester(f, g, x, 1).
- If the subresultant prs is complete, then the output coincides
- with the Euclidean sequence of the polynomials f, g.
- References:
- ===========
- 1. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants
- and Their Applications. Appl. Algebra in Engin., Communic. and Comp.,
- Vol. 15, 233-266, 2004.
- """
- # make sure neither f nor g is 0
- if f == 0 or g == 0:
- return [f, g]
- n = degF = degree(f, x)
- m = degG = degree(g, x)
- # make sure proper degrees
- if n == 0 and m == 0:
- return [f, g]
- if n < m:
- n, m, degF, degG, f, g = m, n, degG, degF, g, f
- if n > 0 and m == 0:
- return [f, g]
- SR_L = [f, g] # subresultant list
- # form matrix sylvester(f, g, x, 1)
- S = sylvester(f, g, x, 1)
- # pick appropriate submatrices of S
- # and form subresultant polys
- j = m - 1
- while j > 0:
- Sp = S[:, :] # copy of S
- # delete last j rows of coeffs of g
- for ind in range(m + n - j, m + n):
- Sp.row_del(m + n - j)
- # delete last j rows of coeffs of f
- for ind in range(m - j, m):
- Sp.row_del(m - j)
- # evaluate determinants and form coefficients list
- coeff_L, k, l = [], Sp.rows, 0
- while l <= j:
- coeff_L.append(Sp[:, 0:k].det())
- Sp.col_swap(k - 1, k + l)
- l += 1
- # form poly and append to SP_L
- SR_L.append(Poly(coeff_L, x).as_expr())
- j -= 1
- # j = 0
- SR_L.append(S.det())
- return process_matrix_output(SR_L, x)
- def modified_subresultants_sylv(f, g, x):
- """
- The input polynomials f, g are in Z[x] or in Q[x]. It is assumed
- that deg(f) >= deg(g).
- Computes the modified subresultant polynomial remainder sequence (prs)
- of f, g by evaluating determinants of appropriately selected
- submatrices of sylvester(f, g, x, 2). The dimensions of the
- latter are (2*deg(f)) x (2*deg(f)).
- Each coefficient is computed by evaluating the determinant of the
- corresponding submatrix of sylvester(f, g, x, 2).
- If the modified subresultant prs is complete, then the output coincides
- with the Sturmian sequence of the polynomials f, g.
- References:
- ===========
- 1. A. G. Akritas,G.I. Malaschonok and P.S. Vigklas:
- Sturm Sequences and Modified Subresultant Polynomial Remainder
- Sequences. Serdica Journal of Computing, Vol. 8, No 1, 29--46, 2014.
- """
- # make sure neither f nor g is 0
- if f == 0 or g == 0:
- return [f, g]
- n = degF = degree(f, x)
- m = degG = degree(g, x)
- # make sure proper degrees
- if n == 0 and m == 0:
- return [f, g]
- if n < m:
- n, m, degF, degG, f, g = m, n, degG, degF, g, f
- if n > 0 and m == 0:
- return [f, g]
- SR_L = [f, g] # modified subresultant list
- # form matrix sylvester(f, g, x, 2)
- S = sylvester(f, g, x, 2)
- # pick appropriate submatrices of S
- # and form modified subresultant polys
- j = m - 1
- while j > 0:
- # delete last 2*j rows of pairs of coeffs of f, g
- Sp = S[0:2*n - 2*j, :] # copy of first 2*n - 2*j rows of S
- # evaluate determinants and form coefficients list
- coeff_L, k, l = [], Sp.rows, 0
- while l <= j:
- coeff_L.append(Sp[:, 0:k].det())
- Sp.col_swap(k - 1, k + l)
- l += 1
- # form poly and append to SP_L
- SR_L.append(Poly(coeff_L, x).as_expr())
- j -= 1
- # j = 0
- SR_L.append(S.det())
- return process_matrix_output(SR_L, x)
- def res(f, g, x):
- """
- The input polynomials f, g are in Z[x] or in Q[x].
- The output is the resultant of f, g computed by evaluating
- the determinant of the matrix sylvester(f, g, x, 1).
- References:
- ===========
- 1. J. S. Cohen: Computer Algebra and Symbolic Computation
- - Mathematical Methods. A. K. Peters, 2003.
- """
- if f == 0 or g == 0:
- raise PolynomialError("The resultant of %s and %s is not defined" % (f, g))
- else:
- return sylvester(f, g, x, 1).det()
- def res_q(f, g, x):
- """
- The input polynomials f, g are in Z[x] or in Q[x].
- The output is the resultant of f, g computed recursively
- by polynomial divisions in Q[x], using the function rem.
- See Cohen's book p. 281.
- References:
- ===========
- 1. J. S. Cohen: Computer Algebra and Symbolic Computation
- - Mathematical Methods. A. K. Peters, 2003.
- """
- m = degree(f, x)
- n = degree(g, x)
- if m < n:
- return (-1)**(m*n) * res_q(g, f, x)
- elif n == 0: # g is a constant
- return g**m
- else:
- r = rem(f, g, x)
- if r == 0:
- return 0
- else:
- s = degree(r, x)
- l = LC(g, x)
- return (-1)**(m*n) * l**(m-s)*res_q(g, r, x)
- def res_z(f, g, x):
- """
- The input polynomials f, g are in Z[x] or in Q[x].
- The output is the resultant of f, g computed recursively
- by polynomial divisions in Z[x], using the function prem().
- See Cohen's book p. 283.
- References:
- ===========
- 1. J. S. Cohen: Computer Algebra and Symbolic Computation
- - Mathematical Methods. A. K. Peters, 2003.
- """
- m = degree(f, x)
- n = degree(g, x)
- if m < n:
- return (-1)**(m*n) * res_z(g, f, x)
- elif n == 0: # g is a constant
- return g**m
- else:
- r = prem(f, g, x)
- if r == 0:
- return 0
- else:
- delta = m - n + 1
- w = (-1)**(m*n) * res_z(g, r, x)
- s = degree(r, x)
- l = LC(g, x)
- k = delta * n - m + s
- return quo(w, l**k, x)
- def sign_seq(poly_seq, x):
- """
- Given a sequence of polynomials poly_seq, it returns
- the sequence of signs of the leading coefficients of
- the polynomials in poly_seq.
- """
- return [sign(LC(poly_seq[i], x)) for i in range(len(poly_seq))]
- def bezout(p, q, x, method='bz'):
- """
- The input polynomials p, q are in Z[x] or in Q[x]. Let
- mx = max(degree(p, x), degree(q, x)).
- The default option bezout(p, q, x, method='bz') returns Bezout's
- symmetric matrix of p and q, of dimensions (mx) x (mx). The
- determinant of this matrix is equal to the determinant of sylvester2,
- Sylvester's matrix of 1853, whose dimensions are (2*mx) x (2*mx);
- however the subresultants of these two matrices may differ.
- The other option, bezout(p, q, x, 'prs'), is of interest to us
- in this module because it returns a matrix equivalent to sylvester2.
- In this case all subresultants of the two matrices are identical.
- Both the subresultant polynomial remainder sequence (prs) and
- the modified subresultant prs of p and q can be computed by
- evaluating determinants of appropriately selected submatrices of
- bezout(p, q, x, 'prs') --- one determinant per coefficient of the
- remainder polynomials.
- The matrices bezout(p, q, x, 'bz') and bezout(p, q, x, 'prs')
- are related by the formula
- bezout(p, q, x, 'prs') =
- backward_eye(deg(p)) * bezout(p, q, x, 'bz') * backward_eye(deg(p)),
- where backward_eye() is the backward identity function.
- References
- ==========
- 1. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants
- and Their Applications. Appl. Algebra in Engin., Communic. and Comp.,
- Vol. 15, 233-266, 2004.
- """
- # obtain degrees of polys
- m, n = degree( Poly(p, x), x), degree( Poly(q, x), x)
- # Special cases:
- # A:: case m = n < 0 (i.e. both polys are 0)
- if m == n and n < 0:
- return Matrix([])
- # B:: case m = n = 0 (i.e. both polys are constants)
- if m == n and n == 0:
- return Matrix([])
- # C:: m == 0 and n < 0 or m < 0 and n == 0
- # (i.e. one poly is constant and the other is 0)
- if m == 0 and n < 0:
- return Matrix([])
- elif m < 0 and n == 0:
- return Matrix([])
- # D:: m >= 1 and n < 0 or m < 0 and n >=1
- # (i.e. one poly is of degree >=1 and the other is 0)
- if m >= 1 and n < 0:
- return Matrix([0])
- elif m < 0 and n >= 1:
- return Matrix([0])
- y = var('y')
- # expr is 0 when x = y
- expr = p * q.subs({x:y}) - p.subs({x:y}) * q
- # hence expr is exactly divisible by x - y
- poly = Poly( quo(expr, x-y), x, y)
- # form Bezout matrix and store them in B as indicated to get
- # the LC coefficient of each poly either in the first position
- # of each row (method='prs') or in the last (method='bz').
- mx = max(m, n)
- B = zeros(mx)
- for i in range(mx):
- for j in range(mx):
- if method == 'prs':
- B[mx - 1 - i, mx - 1 - j] = poly.nth(i, j)
- else:
- B[i, j] = poly.nth(i, j)
- return B
- def backward_eye(n):
- '''
- Returns the backward identity matrix of dimensions n x n.
- Needed to "turn" the Bezout matrices
- so that the leading coefficients are first.
- See docstring of the function bezout(p, q, x, method='bz').
- '''
- M = eye(n) # identity matrix of order n
- for i in range(int(M.rows / 2)):
- M.row_swap(0 + i, M.rows - 1 - i)
- return M
- def subresultants_bezout(p, q, x):
- """
- The input polynomials p, q are in Z[x] or in Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the subresultant polynomial remainder sequence
- of p, q by evaluating determinants of appropriately selected
- submatrices of bezout(p, q, x, 'prs'). The dimensions of the
- latter are deg(p) x deg(p).
- Each coefficient is computed by evaluating the determinant of the
- corresponding submatrix of bezout(p, q, x, 'prs').
- bezout(p, q, x, 'prs) is used instead of sylvester(p, q, x, 1),
- Sylvester's matrix of 1840, because the dimensions of the latter
- are (deg(p) + deg(q)) x (deg(p) + deg(q)).
- If the subresultant prs is complete, then the output coincides
- with the Euclidean sequence of the polynomials p, q.
- References
- ==========
- 1. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants
- and Their Applications. Appl. Algebra in Engin., Communic. and Comp.,
- Vol. 15, 233-266, 2004.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- f, g = p, q
- n = degF = degree(f, x)
- m = degG = degree(g, x)
- # make sure proper degrees
- if n == 0 and m == 0:
- return [f, g]
- if n < m:
- n, m, degF, degG, f, g = m, n, degG, degF, g, f
- if n > 0 and m == 0:
- return [f, g]
- SR_L = [f, g] # subresultant list
- F = LC(f, x)**(degF - degG)
- # form the bezout matrix
- B = bezout(f, g, x, 'prs')
- # pick appropriate submatrices of B
- # and form subresultant polys
- if degF > degG:
- j = 2
- if degF == degG:
- j = 1
- while j <= degF:
- M = B[0:j, :]
- k, coeff_L = j - 1, []
- while k <= degF - 1:
- coeff_L.append(M[:, 0:j].det())
- if k < degF - 1:
- M.col_swap(j - 1, k + 1)
- k = k + 1
- # apply Theorem 2.1 in the paper by Toca & Vega 2004
- # to get correct signs
- SR_L.append(int((-1)**(j*(j-1)/2)) * (Poly(coeff_L, x) / F).as_expr())
- j = j + 1
- return process_matrix_output(SR_L, x)
- def modified_subresultants_bezout(p, q, x):
- """
- The input polynomials p, q are in Z[x] or in Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the modified subresultant polynomial remainder sequence
- of p, q by evaluating determinants of appropriately selected
- submatrices of bezout(p, q, x, 'prs'). The dimensions of the
- latter are deg(p) x deg(p).
- Each coefficient is computed by evaluating the determinant of the
- corresponding submatrix of bezout(p, q, x, 'prs').
- bezout(p, q, x, 'prs') is used instead of sylvester(p, q, x, 2),
- Sylvester's matrix of 1853, because the dimensions of the latter
- are 2*deg(p) x 2*deg(p).
- If the modified subresultant prs is complete, and LC( p ) > 0, the output
- coincides with the (generalized) Sturm's sequence of the polynomials p, q.
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
- and Modified Subresultant Polynomial Remainder Sequences.''
- Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
- 2. G.M.Diaz-Toca,L.Gonzalez-Vega: Various New Expressions for Subresultants
- and Their Applications. Appl. Algebra in Engin., Communic. and Comp.,
- Vol. 15, 233-266, 2004.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- f, g = p, q
- n = degF = degree(f, x)
- m = degG = degree(g, x)
- # make sure proper degrees
- if n == 0 and m == 0:
- return [f, g]
- if n < m:
- n, m, degF, degG, f, g = m, n, degG, degF, g, f
- if n > 0 and m == 0:
- return [f, g]
- SR_L = [f, g] # subresultant list
- # form the bezout matrix
- B = bezout(f, g, x, 'prs')
- # pick appropriate submatrices of B
- # and form subresultant polys
- if degF > degG:
- j = 2
- if degF == degG:
- j = 1
- while j <= degF:
- M = B[0:j, :]
- k, coeff_L = j - 1, []
- while k <= degF - 1:
- coeff_L.append(M[:, 0:j].det())
- if k < degF - 1:
- M.col_swap(j - 1, k + 1)
- k = k + 1
- ## Theorem 2.1 in the paper by Toca & Vega 2004 is _not needed_
- ## in this case since
- ## the bezout matrix is equivalent to sylvester2
- SR_L.append(( Poly(coeff_L, x)).as_expr())
- j = j + 1
- return process_matrix_output(SR_L, x)
- def sturm_pg(p, q, x, method=0):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the (generalized) Sturm sequence of p and q in Z[x] or Q[x].
- If q = diff(p, x, 1) it is the usual Sturm sequence.
- A. If method == 0, default, the remainder coefficients of the sequence
- are (in absolute value) ``modified'' subresultants, which for non-monic
- polynomials are greater than the coefficients of the corresponding
- subresultants by the factor Abs(LC(p)**( deg(p)- deg(q))).
- B. If method == 1, the remainder coefficients of the sequence are (in
- absolute value) subresultants, which for non-monic polynomials are
- smaller than the coefficients of the corresponding ``modified''
- subresultants by the factor Abs(LC(p)**( deg(p)- deg(q))).
- If the Sturm sequence is complete, method=0 and LC( p ) > 0, the coefficients
- of the polynomials in the sequence are ``modified'' subresultants.
- That is, they are determinants of appropriately selected submatrices of
- sylvester2, Sylvester's matrix of 1853. In this case the Sturm sequence
- coincides with the ``modified'' subresultant prs, of the polynomials
- p, q.
- If the Sturm sequence is incomplete and method=0 then the signs of the
- coefficients of the polynomials in the sequence may differ from the signs
- of the coefficients of the corresponding polynomials in the ``modified''
- subresultant prs; however, the absolute values are the same.
- To compute the coefficients, no determinant evaluation takes place. Instead,
- polynomial divisions in Q[x] are performed, using the function rem(p, q, x);
- the coefficients of the remainders computed this way become (``modified'')
- subresultants with the help of the Pell-Gordon Theorem of 1917.
- See also the function euclid_pg(p, q, x).
- References
- ==========
- 1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding
- the Highest Common Factor of Two Polynomials. Annals of MatheMatics,
- Second Series, 18 (1917), No. 4, 188-193.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
- and Modified Subresultant Polynomial Remainder Sequences.''
- Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- # make sure proper degrees
- d0 = degree(p, x)
- d1 = degree(q, x)
- if d0 == 0 and d1 == 0:
- return [p, q]
- if d1 > d0:
- d0, d1 = d1, d0
- p, q = q, p
- if d0 > 0 and d1 == 0:
- return [p,q]
- # make sure LC(p) > 0
- flag = 0
- if LC(p,x) < 0:
- flag = 1
- p = -p
- q = -q
- # initialize
- lcf = LC(p, x)**(d0 - d1) # lcf * subr = modified subr
- a0, a1 = p, q # the input polys
- sturm_seq = [a0, a1] # the output list
- del0 = d0 - d1 # degree difference
- rho1 = LC(a1, x) # leading coeff of a1
- exp_deg = d1 - 1 # expected degree of a2
- a2 = - rem(a0, a1, domain=QQ) # first remainder
- rho2 = LC(a2,x) # leading coeff of a2
- d2 = degree(a2, x) # actual degree of a2
- deg_diff_new = exp_deg - d2 # expected - actual degree
- del1 = d1 - d2 # degree difference
- # mul_fac is the factor by which a2 is multiplied to
- # get integer coefficients
- mul_fac_old = rho1**(del0 + del1 - deg_diff_new)
- # append accordingly
- if method == 0:
- sturm_seq.append( simplify(lcf * a2 * Abs(mul_fac_old)))
- else:
- sturm_seq.append( simplify( a2 * Abs(mul_fac_old)))
- # main loop
- deg_diff_old = deg_diff_new
- while d2 > 0:
- a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
- del0 = del1 # update degree difference
- exp_deg = d1 - 1 # new expected degree
- a2 = - rem(a0, a1, domain=QQ) # new remainder
- rho3 = LC(a2, x) # leading coeff of a2
- d2 = degree(a2, x) # actual degree of a2
- deg_diff_new = exp_deg - d2 # expected - actual degree
- del1 = d1 - d2 # degree difference
- # take into consideration the power
- # rho1**deg_diff_old that was "left out"
- expo_old = deg_diff_old # rho1 raised to this power
- expo_new = del0 + del1 - deg_diff_new # rho2 raised to this power
- # update variables and append
- mul_fac_new = rho2**(expo_new) * rho1**(expo_old) * mul_fac_old
- deg_diff_old, mul_fac_old = deg_diff_new, mul_fac_new
- rho1, rho2 = rho2, rho3
- if method == 0:
- sturm_seq.append( simplify(lcf * a2 * Abs(mul_fac_old)))
- else:
- sturm_seq.append( simplify( a2 * Abs(mul_fac_old)))
- if flag: # change the sign of the sequence
- sturm_seq = [-i for i in sturm_seq]
- # gcd is of degree > 0 ?
- m = len(sturm_seq)
- if sturm_seq[m - 1] == nan or sturm_seq[m - 1] == 0:
- sturm_seq.pop(m - 1)
- return sturm_seq
- def sturm_q(p, q, x):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the (generalized) Sturm sequence of p and q in Q[x].
- Polynomial divisions in Q[x] are performed, using the function rem(p, q, x).
- The coefficients of the polynomials in the Sturm sequence can be uniquely
- determined from the corresponding coefficients of the polynomials found
- either in:
- (a) the ``modified'' subresultant prs, (references 1, 2)
- or in
- (b) the subresultant prs (reference 3).
- References
- ==========
- 1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding
- the Highest Common Factor of Two Polynomials. Annals of MatheMatics,
- Second Series, 18 (1917), No. 4, 188-193.
- 2 Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
- and Modified Subresultant Polynomial Remainder Sequences.''
- Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
- 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
- on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- # make sure proper degrees
- d0 = degree(p, x)
- d1 = degree(q, x)
- if d0 == 0 and d1 == 0:
- return [p, q]
- if d1 > d0:
- d0, d1 = d1, d0
- p, q = q, p
- if d0 > 0 and d1 == 0:
- return [p,q]
- # make sure LC(p) > 0
- flag = 0
- if LC(p,x) < 0:
- flag = 1
- p = -p
- q = -q
- # initialize
- a0, a1 = p, q # the input polys
- sturm_seq = [a0, a1] # the output list
- a2 = -rem(a0, a1, domain=QQ) # first remainder
- d2 = degree(a2, x) # degree of a2
- sturm_seq.append( a2 )
- # main loop
- while d2 > 0:
- a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
- a2 = -rem(a0, a1, domain=QQ) # new remainder
- d2 = degree(a2, x) # actual degree of a2
- sturm_seq.append( a2 )
- if flag: # change the sign of the sequence
- sturm_seq = [-i for i in sturm_seq]
- # gcd is of degree > 0 ?
- m = len(sturm_seq)
- if sturm_seq[m - 1] == nan or sturm_seq[m - 1] == 0:
- sturm_seq.pop(m - 1)
- return sturm_seq
- def sturm_amv(p, q, x, method=0):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the (generalized) Sturm sequence of p and q in Z[x] or Q[x].
- If q = diff(p, x, 1) it is the usual Sturm sequence.
- A. If method == 0, default, the remainder coefficients of the
- sequence are (in absolute value) ``modified'' subresultants, which
- for non-monic polynomials are greater than the coefficients of the
- corresponding subresultants by the factor Abs(LC(p)**( deg(p)- deg(q))).
- B. If method == 1, the remainder coefficients of the sequence are (in
- absolute value) subresultants, which for non-monic polynomials are
- smaller than the coefficients of the corresponding ``modified''
- subresultants by the factor Abs( LC(p)**( deg(p)- deg(q)) ).
- If the Sturm sequence is complete, method=0 and LC( p ) > 0, then the
- coefficients of the polynomials in the sequence are ``modified'' subresultants.
- That is, they are determinants of appropriately selected submatrices of
- sylvester2, Sylvester's matrix of 1853. In this case the Sturm sequence
- coincides with the ``modified'' subresultant prs, of the polynomials
- p, q.
- If the Sturm sequence is incomplete and method=0 then the signs of the
- coefficients of the polynomials in the sequence may differ from the signs
- of the coefficients of the corresponding polynomials in the ``modified''
- subresultant prs; however, the absolute values are the same.
- To compute the coefficients, no determinant evaluation takes place.
- Instead, we first compute the euclidean sequence of p and q using
- euclid_amv(p, q, x) and then: (a) change the signs of the remainders in the
- Euclidean sequence according to the pattern "-, -, +, +, -, -, +, +,..."
- (see Lemma 1 in the 1st reference or Theorem 3 in the 2nd reference)
- and (b) if method=0, assuming deg(p) > deg(q), we multiply the remainder
- coefficients of the Euclidean sequence times the factor
- Abs( LC(p)**( deg(p)- deg(q)) ) to make them modified subresultants.
- See also the function sturm_pg(p, q, x).
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
- on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On the Remainders
- Obtained in Finding the Greatest Common Divisor of Two Polynomials.'' Serdica
- Journal of Computing 9(2) (2015), 123-138.
- 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
- Remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
- Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
- """
- # compute the euclidean sequence
- prs = euclid_amv(p, q, x)
- # defensive
- if prs == [] or len(prs) == 2:
- return prs
- # the coefficients in prs are subresultants and hence are smaller
- # than the corresponding subresultants by the factor
- # Abs( LC(prs[0])**( deg(prs[0]) - deg(prs[1])) ); Theorem 2, 2nd reference.
- lcf = Abs( LC(prs[0])**( degree(prs[0], x) - degree(prs[1], x) ) )
- # the signs of the first two polys in the sequence stay the same
- sturm_seq = [prs[0], prs[1]]
- # change the signs according to "-, -, +, +, -, -, +, +,..."
- # and multiply times lcf if needed
- flag = 0
- m = len(prs)
- i = 2
- while i <= m-1:
- if flag == 0:
- sturm_seq.append( - prs[i] )
- i = i + 1
- if i == m:
- break
- sturm_seq.append( - prs[i] )
- i = i + 1
- flag = 1
- elif flag == 1:
- sturm_seq.append( prs[i] )
- i = i + 1
- if i == m:
- break
- sturm_seq.append( prs[i] )
- i = i + 1
- flag = 0
- # subresultants or modified subresultants?
- if method == 0 and lcf > 1:
- aux_seq = [sturm_seq[0], sturm_seq[1]]
- for i in range(2, m):
- aux_seq.append(simplify(sturm_seq[i] * lcf ))
- sturm_seq = aux_seq
- return sturm_seq
- def euclid_pg(p, q, x):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the Euclidean sequence of p and q in Z[x] or Q[x].
- If the Euclidean sequence is complete the coefficients of the polynomials
- in the sequence are subresultants. That is, they are determinants of
- appropriately selected submatrices of sylvester1, Sylvester's matrix of 1840.
- In this case the Euclidean sequence coincides with the subresultant prs
- of the polynomials p, q.
- If the Euclidean sequence is incomplete the signs of the coefficients of the
- polynomials in the sequence may differ from the signs of the coefficients of
- the corresponding polynomials in the subresultant prs; however, the absolute
- values are the same.
- To compute the Euclidean sequence, no determinant evaluation takes place.
- We first compute the (generalized) Sturm sequence of p and q using
- sturm_pg(p, q, x, 1), in which case the coefficients are (in absolute value)
- equal to subresultants. Then we change the signs of the remainders in the
- Sturm sequence according to the pattern "-, -, +, +, -, -, +, +,..." ;
- see Lemma 1 in the 1st reference or Theorem 3 in the 2nd reference as well as
- the function sturm_pg(p, q, x).
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
- on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On the Remainders
- Obtained in Finding the Greatest Common Divisor of Two Polynomials.'' Serdica
- Journal of Computing 9(2) (2015), 123-138.
- 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
- Remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
- Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
- """
- # compute the sturmian sequence using the Pell-Gordon (or AMV) theorem
- # with the coefficients in the prs being (in absolute value) subresultants
- prs = sturm_pg(p, q, x, 1) ## any other method would do
- # defensive
- if prs == [] or len(prs) == 2:
- return prs
- # the signs of the first two polys in the sequence stay the same
- euclid_seq = [prs[0], prs[1]]
- # change the signs according to "-, -, +, +, -, -, +, +,..."
- flag = 0
- m = len(prs)
- i = 2
- while i <= m-1:
- if flag == 0:
- euclid_seq.append(- prs[i] )
- i = i + 1
- if i == m:
- break
- euclid_seq.append(- prs[i] )
- i = i + 1
- flag = 1
- elif flag == 1:
- euclid_seq.append(prs[i] )
- i = i + 1
- if i == m:
- break
- euclid_seq.append(prs[i] )
- i = i + 1
- flag = 0
- return euclid_seq
- def euclid_q(p, q, x):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the Euclidean sequence of p and q in Q[x].
- Polynomial divisions in Q[x] are performed, using the function rem(p, q, x).
- The coefficients of the polynomials in the Euclidean sequence can be uniquely
- determined from the corresponding coefficients of the polynomials found
- either in:
- (a) the ``modified'' subresultant polynomial remainder sequence,
- (references 1, 2)
- or in
- (b) the subresultant polynomial remainder sequence (references 3).
- References
- ==========
- 1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding
- the Highest Common Factor of Two Polynomials. Annals of MatheMatics,
- Second Series, 18 (1917), No. 4, 188-193.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
- and Modified Subresultant Polynomial Remainder Sequences.''
- Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
- 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
- on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- # make sure proper degrees
- d0 = degree(p, x)
- d1 = degree(q, x)
- if d0 == 0 and d1 == 0:
- return [p, q]
- if d1 > d0:
- d0, d1 = d1, d0
- p, q = q, p
- if d0 > 0 and d1 == 0:
- return [p,q]
- # make sure LC(p) > 0
- flag = 0
- if LC(p,x) < 0:
- flag = 1
- p = -p
- q = -q
- # initialize
- a0, a1 = p, q # the input polys
- euclid_seq = [a0, a1] # the output list
- a2 = rem(a0, a1, domain=QQ) # first remainder
- d2 = degree(a2, x) # degree of a2
- euclid_seq.append( a2 )
- # main loop
- while d2 > 0:
- a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
- a2 = rem(a0, a1, domain=QQ) # new remainder
- d2 = degree(a2, x) # actual degree of a2
- euclid_seq.append( a2 )
- if flag: # change the sign of the sequence
- euclid_seq = [-i for i in euclid_seq]
- # gcd is of degree > 0 ?
- m = len(euclid_seq)
- if euclid_seq[m - 1] == nan or euclid_seq[m - 1] == 0:
- euclid_seq.pop(m - 1)
- return euclid_seq
- def euclid_amv(f, g, x):
- """
- f, g are polynomials in Z[x] or Q[x]. It is assumed
- that degree(f, x) >= degree(g, x).
- Computes the Euclidean sequence of p and q in Z[x] or Q[x].
- If the Euclidean sequence is complete the coefficients of the polynomials
- in the sequence are subresultants. That is, they are determinants of
- appropriately selected submatrices of sylvester1, Sylvester's matrix of 1840.
- In this case the Euclidean sequence coincides with the subresultant prs,
- of the polynomials p, q.
- If the Euclidean sequence is incomplete the signs of the coefficients of the
- polynomials in the sequence may differ from the signs of the coefficients of
- the corresponding polynomials in the subresultant prs; however, the absolute
- values are the same.
- To compute the coefficients, no determinant evaluation takes place.
- Instead, polynomial divisions in Z[x] or Q[x] are performed, using
- the function rem_z(f, g, x); the coefficients of the remainders
- computed this way become subresultants with the help of the
- Collins-Brown-Traub formula for coefficient reduction.
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
- on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
- remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
- Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
- """
- # make sure neither f nor g is 0
- if f == 0 or g == 0:
- return [f, g]
- # make sure proper degrees
- d0 = degree(f, x)
- d1 = degree(g, x)
- if d0 == 0 and d1 == 0:
- return [f, g]
- if d1 > d0:
- d0, d1 = d1, d0
- f, g = g, f
- if d0 > 0 and d1 == 0:
- return [f, g]
- # initialize
- a0 = f
- a1 = g
- euclid_seq = [a0, a1]
- deg_dif_p1, c = degree(a0, x) - degree(a1, x) + 1, -1
- # compute the first polynomial of the prs
- i = 1
- a2 = rem_z(a0, a1, x) / Abs( (-1)**deg_dif_p1 ) # first remainder
- euclid_seq.append( a2 )
- d2 = degree(a2, x) # actual degree of a2
- # main loop
- while d2 >= 1:
- a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
- i += 1
- sigma0 = -LC(a0)
- c = (sigma0**(deg_dif_p1 - 1)) / (c**(deg_dif_p1 - 2))
- deg_dif_p1 = degree(a0, x) - d2 + 1
- a2 = rem_z(a0, a1, x) / Abs( (c**(deg_dif_p1 - 1)) * sigma0 )
- euclid_seq.append( a2 )
- d2 = degree(a2, x) # actual degree of a2
- # gcd is of degree > 0 ?
- m = len(euclid_seq)
- if euclid_seq[m - 1] == nan or euclid_seq[m - 1] == 0:
- euclid_seq.pop(m - 1)
- return euclid_seq
- def modified_subresultants_pg(p, q, x):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the ``modified'' subresultant prs of p and q in Z[x] or Q[x];
- the coefficients of the polynomials in the sequence are
- ``modified'' subresultants. That is, they are determinants of appropriately
- selected submatrices of sylvester2, Sylvester's matrix of 1853.
- To compute the coefficients, no determinant evaluation takes place. Instead,
- polynomial divisions in Q[x] are performed, using the function rem(p, q, x);
- the coefficients of the remainders computed this way become ``modified''
- subresultants with the help of the Pell-Gordon Theorem of 1917.
- If the ``modified'' subresultant prs is complete, and LC( p ) > 0, it coincides
- with the (generalized) Sturm sequence of the polynomials p, q.
- References
- ==========
- 1. Pell A. J., R. L. Gordon. The Modified Remainders Obtained in Finding
- the Highest Common Factor of Two Polynomials. Annals of MatheMatics,
- Second Series, 18 (1917), No. 4, 188-193.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
- and Modified Subresultant Polynomial Remainder Sequences.''
- Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- # make sure proper degrees
- d0 = degree(p,x)
- d1 = degree(q,x)
- if d0 == 0 and d1 == 0:
- return [p, q]
- if d1 > d0:
- d0, d1 = d1, d0
- p, q = q, p
- if d0 > 0 and d1 == 0:
- return [p,q]
- # initialize
- k = var('k') # index in summation formula
- u_list = [] # of elements (-1)**u_i
- subres_l = [p, q] # mod. subr. prs output list
- a0, a1 = p, q # the input polys
- del0 = d0 - d1 # degree difference
- degdif = del0 # save it
- rho_1 = LC(a0) # lead. coeff (a0)
- # Initialize Pell-Gordon variables
- rho_list_minus_1 = sign( LC(a0, x)) # sign of LC(a0)
- rho1 = LC(a1, x) # leading coeff of a1
- rho_list = [ sign(rho1)] # of signs
- p_list = [del0] # of degree differences
- u = summation(k, (k, 1, p_list[0])) # value of u
- u_list.append(u) # of u values
- v = sum(p_list) # v value
- # first remainder
- exp_deg = d1 - 1 # expected degree of a2
- a2 = - rem(a0, a1, domain=QQ) # first remainder
- rho2 = LC(a2, x) # leading coeff of a2
- d2 = degree(a2, x) # actual degree of a2
- deg_diff_new = exp_deg - d2 # expected - actual degree
- del1 = d1 - d2 # degree difference
- # mul_fac is the factor by which a2 is multiplied to
- # get integer coefficients
- mul_fac_old = rho1**(del0 + del1 - deg_diff_new)
- # update Pell-Gordon variables
- p_list.append(1 + deg_diff_new) # deg_diff_new is 0 for complete seq
- # apply Pell-Gordon formula (7) in second reference
- num = 1 # numerator of fraction
- for u in u_list:
- num *= (-1)**u
- num = num * (-1)**v
- # denominator depends on complete / incomplete seq
- if deg_diff_new == 0: # complete seq
- den = 1
- for k in range(len(rho_list)):
- den *= rho_list[k]**(p_list[k] + p_list[k + 1])
- den = den * rho_list_minus_1
- else: # incomplete seq
- den = 1
- for k in range(len(rho_list)-1):
- den *= rho_list[k]**(p_list[k] + p_list[k + 1])
- den = den * rho_list_minus_1
- expo = (p_list[len(rho_list) - 1] + p_list[len(rho_list)] - deg_diff_new)
- den = den * rho_list[len(rho_list) - 1]**expo
- # the sign of the determinant depends on sg(num / den)
- if sign(num / den) > 0:
- subres_l.append( simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) )
- else:
- subres_l.append(- simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) )
- # update Pell-Gordon variables
- k = var('k')
- rho_list.append( sign(rho2))
- u = summation(k, (k, 1, p_list[len(p_list) - 1]))
- u_list.append(u)
- v = sum(p_list)
- deg_diff_old=deg_diff_new
- # main loop
- while d2 > 0:
- a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
- del0 = del1 # update degree difference
- exp_deg = d1 - 1 # new expected degree
- a2 = - rem(a0, a1, domain=QQ) # new remainder
- rho3 = LC(a2, x) # leading coeff of a2
- d2 = degree(a2, x) # actual degree of a2
- deg_diff_new = exp_deg - d2 # expected - actual degree
- del1 = d1 - d2 # degree difference
- # take into consideration the power
- # rho1**deg_diff_old that was "left out"
- expo_old = deg_diff_old # rho1 raised to this power
- expo_new = del0 + del1 - deg_diff_new # rho2 raised to this power
- mul_fac_new = rho2**(expo_new) * rho1**(expo_old) * mul_fac_old
- # update variables
- deg_diff_old, mul_fac_old = deg_diff_new, mul_fac_new
- rho1, rho2 = rho2, rho3
- # update Pell-Gordon variables
- p_list.append(1 + deg_diff_new) # deg_diff_new is 0 for complete seq
- # apply Pell-Gordon formula (7) in second reference
- num = 1 # numerator
- for u in u_list:
- num *= (-1)**u
- num = num * (-1)**v
- # denominator depends on complete / incomplete seq
- if deg_diff_new == 0: # complete seq
- den = 1
- for k in range(len(rho_list)):
- den *= rho_list[k]**(p_list[k] + p_list[k + 1])
- den = den * rho_list_minus_1
- else: # incomplete seq
- den = 1
- for k in range(len(rho_list)-1):
- den *= rho_list[k]**(p_list[k] + p_list[k + 1])
- den = den * rho_list_minus_1
- expo = (p_list[len(rho_list) - 1] + p_list[len(rho_list)] - deg_diff_new)
- den = den * rho_list[len(rho_list) - 1]**expo
- # the sign of the determinant depends on sg(num / den)
- if sign(num / den) > 0:
- subres_l.append( simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) )
- else:
- subres_l.append(- simplify(rho_1**degdif*a2* Abs(mul_fac_old) ) )
- # update Pell-Gordon variables
- k = var('k')
- rho_list.append( sign(rho2))
- u = summation(k, (k, 1, p_list[len(p_list) - 1]))
- u_list.append(u)
- v = sum(p_list)
- # gcd is of degree > 0 ?
- m = len(subres_l)
- if subres_l[m - 1] == nan or subres_l[m - 1] == 0:
- subres_l.pop(m - 1)
- # LC( p ) < 0
- m = len(subres_l) # list may be shorter now due to deg(gcd ) > 0
- if LC( p ) < 0:
- aux_seq = [subres_l[0], subres_l[1]]
- for i in range(2, m):
- aux_seq.append(simplify(subres_l[i] * (-1) ))
- subres_l = aux_seq
- return subres_l
- def subresultants_pg(p, q, x):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the subresultant prs of p and q in Z[x] or Q[x], from
- the modified subresultant prs of p and q.
- The coefficients of the polynomials in these two sequences differ only
- in sign and the factor LC(p)**( deg(p)- deg(q)) as stated in
- Theorem 2 of the reference.
- The coefficients of the polynomials in the output sequence are
- subresultants. That is, they are determinants of appropriately
- selected submatrices of sylvester1, Sylvester's matrix of 1840.
- If the subresultant prs is complete, then it coincides with the
- Euclidean sequence of the polynomials p, q.
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: "On the Remainders
- Obtained in Finding the Greatest Common Divisor of Two Polynomials."
- Serdica Journal of Computing 9(2) (2015), 123-138.
- """
- # compute the modified subresultant prs
- lst = modified_subresultants_pg(p,q,x) ## any other method would do
- # defensive
- if lst == [] or len(lst) == 2:
- return lst
- # the coefficients in lst are modified subresultants and, hence, are
- # greater than those of the corresponding subresultants by the factor
- # LC(lst[0])**( deg(lst[0]) - deg(lst[1])); see Theorem 2 in reference.
- lcf = LC(lst[0])**( degree(lst[0], x) - degree(lst[1], x) )
- # Initialize the subresultant prs list
- subr_seq = [lst[0], lst[1]]
- # compute the degree sequences m_i and j_i of Theorem 2 in reference.
- deg_seq = [degree(Poly(poly, x), x) for poly in lst]
- deg = deg_seq[0]
- deg_seq_s = deg_seq[1:-1]
- m_seq = [m-1 for m in deg_seq_s]
- j_seq = [deg - m for m in m_seq]
- # compute the AMV factors of Theorem 2 in reference.
- fact = [(-1)**( j*(j-1)/S(2) ) for j in j_seq]
- # shortened list without the first two polys
- lst_s = lst[2:]
- # poly lst_s[k] is multiplied times fact[k], divided by lcf
- # and appended to the subresultant prs list
- m = len(fact)
- for k in range(m):
- if sign(fact[k]) == -1:
- subr_seq.append(-lst_s[k] / lcf)
- else:
- subr_seq.append(lst_s[k] / lcf)
- return subr_seq
- def subresultants_amv_q(p, q, x):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the subresultant prs of p and q in Q[x];
- the coefficients of the polynomials in the sequence are
- subresultants. That is, they are determinants of appropriately
- selected submatrices of sylvester1, Sylvester's matrix of 1840.
- To compute the coefficients, no determinant evaluation takes place.
- Instead, polynomial divisions in Q[x] are performed, using the
- function rem(p, q, x); the coefficients of the remainders
- computed this way become subresultants with the help of the
- Akritas-Malaschonok-Vigklas Theorem of 2015.
- If the subresultant prs is complete, then it coincides with the
- Euclidean sequence of the polynomials p, q.
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
- on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
- remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
- Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- # make sure proper degrees
- d0 = degree(p, x)
- d1 = degree(q, x)
- if d0 == 0 and d1 == 0:
- return [p, q]
- if d1 > d0:
- d0, d1 = d1, d0
- p, q = q, p
- if d0 > 0 and d1 == 0:
- return [p, q]
- # initialize
- i, s = 0, 0 # counters for remainders & odd elements
- p_odd_index_sum = 0 # contains the sum of p_1, p_3, etc
- subres_l = [p, q] # subresultant prs output list
- a0, a1 = p, q # the input polys
- sigma1 = LC(a1, x) # leading coeff of a1
- p0 = d0 - d1 # degree difference
- if p0 % 2 == 1:
- s += 1
- phi = floor( (s + 1) / 2 )
- mul_fac = 1
- d2 = d1
- # main loop
- while d2 > 0:
- i += 1
- a2 = rem(a0, a1, domain= QQ) # new remainder
- if i == 1:
- sigma2 = LC(a2, x)
- else:
- sigma3 = LC(a2, x)
- sigma1, sigma2 = sigma2, sigma3
- d2 = degree(a2, x)
- p1 = d1 - d2
- psi = i + phi + p_odd_index_sum
- # new mul_fac
- mul_fac = sigma1**(p0 + 1) * mul_fac
- ## compute the sign of the first fraction in formula (9) of the paper
- # numerator
- num = (-1)**psi
- # denominator
- den = sign(mul_fac)
- # the sign of the determinant depends on sign( num / den ) != 0
- if sign(num / den) > 0:
- subres_l.append( simplify(expand(a2* Abs(mul_fac))))
- else:
- subres_l.append(- simplify(expand(a2* Abs(mul_fac))))
- ## bring into mul_fac the missing power of sigma if there was a degree gap
- if p1 - 1 > 0:
- mul_fac = mul_fac * sigma1**(p1 - 1)
- # update AMV variables
- a0, a1, d0, d1 = a1, a2, d1, d2
- p0 = p1
- if p0 % 2 ==1:
- s += 1
- phi = floor( (s + 1) / 2 )
- if i%2 == 1:
- p_odd_index_sum += p0 # p_i has odd index
- # gcd is of degree > 0 ?
- m = len(subres_l)
- if subres_l[m - 1] == nan or subres_l[m - 1] == 0:
- subres_l.pop(m - 1)
- return subres_l
- def compute_sign(base, expo):
- '''
- base != 0 and expo >= 0 are integers;
- returns the sign of base**expo without
- evaluating the power itself!
- '''
- sb = sign(base)
- if sb == 1:
- return 1
- pe = expo % 2
- if pe == 0:
- return -sb
- else:
- return sb
- def rem_z(p, q, x):
- '''
- Intended mainly for p, q polynomials in Z[x] so that,
- on dividing p by q, the remainder will also be in Z[x]. (However,
- it also works fine for polynomials in Q[x].) It is assumed
- that degree(p, x) >= degree(q, x).
- It premultiplies p by the _absolute_ value of the leading coefficient
- of q, raised to the power deg(p) - deg(q) + 1 and then performs
- polynomial division in Q[x], using the function rem(p, q, x).
- By contrast the function prem(p, q, x) does _not_ use the absolute
- value of the leading coefficient of q.
- This results not only in ``messing up the signs'' of the Euclidean and
- Sturmian prs's as mentioned in the second reference,
- but also in violation of the main results of the first and third
- references --- Theorem 4 and Theorem 1 respectively. Theorems 4 and 1
- establish a one-to-one correspondence between the Euclidean and the
- Sturmian prs of p, q, on one hand, and the subresultant prs of p, q,
- on the other.
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On the Remainders
- Obtained in Finding the Greatest Common Divisor of Two Polynomials.''
- Serdica Journal of Computing, 9(2) (2015), 123-138.
- 2. https://planetMath.org/sturmstheorem
- 3. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result on
- the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
- '''
- if (p.as_poly().is_univariate and q.as_poly().is_univariate and
- p.as_poly().gens == q.as_poly().gens):
- delta = (degree(p, x) - degree(q, x) + 1)
- return rem(Abs(LC(q, x))**delta * p, q, x)
- else:
- return prem(p, q, x)
- def quo_z(p, q, x):
- """
- Intended mainly for p, q polynomials in Z[x] so that,
- on dividing p by q, the quotient will also be in Z[x]. (However,
- it also works fine for polynomials in Q[x].) It is assumed
- that degree(p, x) >= degree(q, x).
- It premultiplies p by the _absolute_ value of the leading coefficient
- of q, raised to the power deg(p) - deg(q) + 1 and then performs
- polynomial division in Q[x], using the function quo(p, q, x).
- By contrast the function pquo(p, q, x) does _not_ use the absolute
- value of the leading coefficient of q.
- See also function rem_z(p, q, x) for additional comments and references.
- """
- if (p.as_poly().is_univariate and q.as_poly().is_univariate and
- p.as_poly().gens == q.as_poly().gens):
- delta = (degree(p, x) - degree(q, x) + 1)
- return quo(Abs(LC(q, x))**delta * p, q, x)
- else:
- return pquo(p, q, x)
- def subresultants_amv(f, g, x):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(f, x) >= degree(g, x).
- Computes the subresultant prs of p and q in Z[x] or Q[x];
- the coefficients of the polynomials in the sequence are
- subresultants. That is, they are determinants of appropriately
- selected submatrices of sylvester1, Sylvester's matrix of 1840.
- To compute the coefficients, no determinant evaluation takes place.
- Instead, polynomial divisions in Z[x] or Q[x] are performed, using
- the function rem_z(p, q, x); the coefficients of the remainders
- computed this way become subresultants with the help of the
- Akritas-Malaschonok-Vigklas Theorem of 2015 and the Collins-Brown-
- Traub formula for coefficient reduction.
- If the subresultant prs is complete, then it coincides with the
- Euclidean sequence of the polynomials p, q.
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``A Basic Result
- on the Theory of Subresultants.'' Serdica Journal of Computing 10 (2016), No.1, 31-48.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Subresultant Polynomial
- remainder Sequences Obtained by Polynomial Divisions in Q[x] or in Z[x].''
- Serdica Journal of Computing 10 (2016), No.3-4, 197-217.
- """
- # make sure neither f nor g is 0
- if f == 0 or g == 0:
- return [f, g]
- # make sure proper degrees
- d0 = degree(f, x)
- d1 = degree(g, x)
- if d0 == 0 and d1 == 0:
- return [f, g]
- if d1 > d0:
- d0, d1 = d1, d0
- f, g = g, f
- if d0 > 0 and d1 == 0:
- return [f, g]
- # initialize
- a0 = f
- a1 = g
- subres_l = [a0, a1]
- deg_dif_p1, c = degree(a0, x) - degree(a1, x) + 1, -1
- # initialize AMV variables
- sigma1 = LC(a1, x) # leading coeff of a1
- i, s = 0, 0 # counters for remainders & odd elements
- p_odd_index_sum = 0 # contains the sum of p_1, p_3, etc
- p0 = deg_dif_p1 - 1
- if p0 % 2 == 1:
- s += 1
- phi = floor( (s + 1) / 2 )
- # compute the first polynomial of the prs
- i += 1
- a2 = rem_z(a0, a1, x) / Abs( (-1)**deg_dif_p1 ) # first remainder
- sigma2 = LC(a2, x) # leading coeff of a2
- d2 = degree(a2, x) # actual degree of a2
- p1 = d1 - d2 # degree difference
- # sgn_den is the factor, the denominator 1st fraction of (9),
- # by which a2 is multiplied to get integer coefficients
- sgn_den = compute_sign( sigma1, p0 + 1 )
- ## compute sign of the 1st fraction in formula (9) of the paper
- # numerator
- psi = i + phi + p_odd_index_sum
- num = (-1)**psi
- # denominator
- den = sgn_den
- # the sign of the determinant depends on sign(num / den) != 0
- if sign(num / den) > 0:
- subres_l.append( a2 )
- else:
- subres_l.append( -a2 )
- # update AMV variable
- if p1 % 2 == 1:
- s += 1
- # bring in the missing power of sigma if there was gap
- if p1 - 1 > 0:
- sgn_den = sgn_den * compute_sign( sigma1, p1 - 1 )
- # main loop
- while d2 >= 1:
- phi = floor( (s + 1) / 2 )
- if i%2 == 1:
- p_odd_index_sum += p1 # p_i has odd index
- a0, a1, d0, d1 = a1, a2, d1, d2 # update polys and degrees
- p0 = p1 # update degree difference
- i += 1
- sigma0 = -LC(a0)
- c = (sigma0**(deg_dif_p1 - 1)) / (c**(deg_dif_p1 - 2))
- deg_dif_p1 = degree(a0, x) - d2 + 1
- a2 = rem_z(a0, a1, x) / Abs( (c**(deg_dif_p1 - 1)) * sigma0 )
- sigma3 = LC(a2, x) # leading coeff of a2
- d2 = degree(a2, x) # actual degree of a2
- p1 = d1 - d2 # degree difference
- psi = i + phi + p_odd_index_sum
- # update variables
- sigma1, sigma2 = sigma2, sigma3
- # new sgn_den
- sgn_den = compute_sign( sigma1, p0 + 1 ) * sgn_den
- # compute the sign of the first fraction in formula (9) of the paper
- # numerator
- num = (-1)**psi
- # denominator
- den = sgn_den
- # the sign of the determinant depends on sign( num / den ) != 0
- if sign(num / den) > 0:
- subres_l.append( a2 )
- else:
- subres_l.append( -a2 )
- # update AMV variable
- if p1 % 2 ==1:
- s += 1
- # bring in the missing power of sigma if there was gap
- if p1 - 1 > 0:
- sgn_den = sgn_den * compute_sign( sigma1, p1 - 1 )
- # gcd is of degree > 0 ?
- m = len(subres_l)
- if subres_l[m - 1] == nan or subres_l[m - 1] == 0:
- subres_l.pop(m - 1)
- return subres_l
- def modified_subresultants_amv(p, q, x):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the modified subresultant prs of p and q in Z[x] or Q[x],
- from the subresultant prs of p and q.
- The coefficients of the polynomials in the two sequences differ only
- in sign and the factor LC(p)**( deg(p)- deg(q)) as stated in
- Theorem 2 of the reference.
- The coefficients of the polynomials in the output sequence are
- modified subresultants. That is, they are determinants of appropriately
- selected submatrices of sylvester2, Sylvester's matrix of 1853.
- If the modified subresultant prs is complete, and LC( p ) > 0, it coincides
- with the (generalized) Sturm's sequence of the polynomials p, q.
- References
- ==========
- 1. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: "On the Remainders
- Obtained in Finding the Greatest Common Divisor of Two Polynomials."
- Serdica Journal of Computing, Serdica Journal of Computing, 9(2) (2015), 123-138.
- """
- # compute the subresultant prs
- lst = subresultants_amv(p,q,x) ## any other method would do
- # defensive
- if lst == [] or len(lst) == 2:
- return lst
- # the coefficients in lst are subresultants and, hence, smaller than those
- # of the corresponding modified subresultants by the factor
- # LC(lst[0])**( deg(lst[0]) - deg(lst[1])); see Theorem 2.
- lcf = LC(lst[0])**( degree(lst[0], x) - degree(lst[1], x) )
- # Initialize the modified subresultant prs list
- subr_seq = [lst[0], lst[1]]
- # compute the degree sequences m_i and j_i of Theorem 2
- deg_seq = [degree(Poly(poly, x), x) for poly in lst]
- deg = deg_seq[0]
- deg_seq_s = deg_seq[1:-1]
- m_seq = [m-1 for m in deg_seq_s]
- j_seq = [deg - m for m in m_seq]
- # compute the AMV factors of Theorem 2
- fact = [(-1)**( j*(j-1)/S(2) ) for j in j_seq]
- # shortened list without the first two polys
- lst_s = lst[2:]
- # poly lst_s[k] is multiplied times fact[k] and times lcf
- # and appended to the subresultant prs list
- m = len(fact)
- for k in range(m):
- if sign(fact[k]) == -1:
- subr_seq.append( simplify(-lst_s[k] * lcf) )
- else:
- subr_seq.append( simplify(lst_s[k] * lcf) )
- return subr_seq
- def correct_sign(deg_f, deg_g, s1, rdel, cdel):
- """
- Used in various subresultant prs algorithms.
- Evaluates the determinant, (a.k.a. subresultant) of a properly selected
- submatrix of s1, Sylvester's matrix of 1840, to get the correct sign
- and value of the leading coefficient of a given polynomial remainder.
- deg_f, deg_g are the degrees of the original polynomials p, q for which the
- matrix s1 = sylvester(p, q, x, 1) was constructed.
- rdel denotes the expected degree of the remainder; it is the number of
- rows to be deleted from each group of rows in s1 as described in the
- reference below.
- cdel denotes the expected degree minus the actual degree of the remainder;
- it is the number of columns to be deleted --- starting with the last column
- forming the square matrix --- from the matrix resulting after the row deletions.
- References
- ==========
- Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``Sturm Sequences
- and Modified Subresultant Polynomial Remainder Sequences.''
- Serdica Journal of Computing, Vol. 8, No 1, 29-46, 2014.
- """
- M = s1[:, :] # copy of matrix s1
- # eliminate rdel rows from the first deg_g rows
- for i in range(M.rows - deg_f - 1, M.rows - deg_f - rdel - 1, -1):
- M.row_del(i)
- # eliminate rdel rows from the last deg_f rows
- for i in range(M.rows - 1, M.rows - rdel - 1, -1):
- M.row_del(i)
- # eliminate cdel columns
- for i in range(cdel):
- M.col_del(M.rows - 1)
- # define submatrix
- Md = M[:, 0: M.rows]
- return Md.det()
- def subresultants_rem(p, q, x):
- """
- p, q are polynomials in Z[x] or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the subresultant prs of p and q in Z[x] or Q[x];
- the coefficients of the polynomials in the sequence are
- subresultants. That is, they are determinants of appropriately
- selected submatrices of sylvester1, Sylvester's matrix of 1840.
- To compute the coefficients polynomial divisions in Q[x] are
- performed, using the function rem(p, q, x). The coefficients
- of the remainders computed this way become subresultants by evaluating
- one subresultant per remainder --- that of the leading coefficient.
- This way we obtain the correct sign and value of the leading coefficient
- of the remainder and we easily ``force'' the rest of the coefficients
- to become subresultants.
- If the subresultant prs is complete, then it coincides with the
- Euclidean sequence of the polynomials p, q.
- References
- ==========
- 1. Akritas, A. G.:``Three New Methods for Computing Subresultant
- Polynomial Remainder Sequences (PRS's).'' Serdica Journal of Computing 9(1) (2015), 1-26.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- # make sure proper degrees
- f, g = p, q
- n = deg_f = degree(f, x)
- m = deg_g = degree(g, x)
- if n == 0 and m == 0:
- return [f, g]
- if n < m:
- n, m, deg_f, deg_g, f, g = m, n, deg_g, deg_f, g, f
- if n > 0 and m == 0:
- return [f, g]
- # initialize
- s1 = sylvester(f, g, x, 1)
- sr_list = [f, g] # subresultant list
- # main loop
- while deg_g > 0:
- r = rem(p, q, x)
- d = degree(r, x)
- if d < 0:
- return sr_list
- # make coefficients subresultants evaluating ONE determinant
- exp_deg = deg_g - 1 # expected degree
- sign_value = correct_sign(n, m, s1, exp_deg, exp_deg - d)
- r = simplify((r / LC(r, x)) * sign_value)
- # append poly with subresultant coeffs
- sr_list.append(r)
- # update degrees and polys
- deg_f, deg_g = deg_g, d
- p, q = q, r
- # gcd is of degree > 0 ?
- m = len(sr_list)
- if sr_list[m - 1] == nan or sr_list[m - 1] == 0:
- sr_list.pop(m - 1)
- return sr_list
- def pivot(M, i, j):
- '''
- M is a matrix, and M[i, j] specifies the pivot element.
- All elements below M[i, j], in the j-th column, will
- be zeroed, if they are not already 0, according to
- Dodgson-Bareiss' integer preserving transformations.
- References
- ==========
- 1. Akritas, A. G.: ``A new method for computing polynomial greatest
- common divisors and polynomial remainder sequences.''
- Numerische MatheMatik 52, 119-127, 1988.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem
- by Van Vleck Regarding Sturm Sequences.''
- Serdica Journal of Computing, 7, No 4, 101-134, 2013.
- '''
- ma = M[:, :] # copy of matrix M
- rs = ma.rows # No. of rows
- cs = ma.cols # No. of cols
- for r in range(i+1, rs):
- if ma[r, j] != 0:
- for c in range(j + 1, cs):
- ma[r, c] = ma[i, j] * ma[r, c] - ma[i, c] * ma[r, j]
- ma[r, j] = 0
- return ma
- def rotate_r(L, k):
- '''
- Rotates right by k. L is a row of a matrix or a list.
- '''
- ll = list(L)
- if ll == []:
- return []
- for i in range(k):
- el = ll.pop(len(ll) - 1)
- ll.insert(0, el)
- return ll if isinstance(L, list) else Matrix([ll])
- def rotate_l(L, k):
- '''
- Rotates left by k. L is a row of a matrix or a list.
- '''
- ll = list(L)
- if ll == []:
- return []
- for i in range(k):
- el = ll.pop(0)
- ll.insert(len(ll) - 1, el)
- return ll if isinstance(L, list) else Matrix([ll])
- def row2poly(row, deg, x):
- '''
- Converts the row of a matrix to a poly of degree deg and variable x.
- Some entries at the beginning and/or at the end of the row may be zero.
- '''
- k = 0
- poly = []
- leng = len(row)
- # find the beginning of the poly ; i.e. the first
- # non-zero element of the row
- while row[k] == 0:
- k = k + 1
- # append the next deg + 1 elements to poly
- for j in range( deg + 1):
- if k + j <= leng:
- poly.append(row[k + j])
- return Poly(poly, x)
- def create_ma(deg_f, deg_g, row1, row2, col_num):
- '''
- Creates a ``small'' matrix M to be triangularized.
- deg_f, deg_g are the degrees of the divident and of the
- divisor polynomials respectively, deg_g > deg_f.
- The coefficients of the divident poly are the elements
- in row2 and those of the divisor poly are the elements
- in row1.
- col_num defines the number of columns of the matrix M.
- '''
- if deg_g - deg_f >= 1:
- print('Reverse degrees')
- return
- m = zeros(deg_f - deg_g + 2, col_num)
- for i in range(deg_f - deg_g + 1):
- m[i, :] = rotate_r(row1, i)
- m[deg_f - deg_g + 1, :] = row2
- return m
- def find_degree(M, deg_f):
- '''
- Finds the degree of the poly corresponding (after triangularization)
- to the _last_ row of the ``small'' matrix M, created by create_ma().
- deg_f is the degree of the divident poly.
- If _last_ row is all 0's returns None.
- '''
- j = deg_f
- for i in range(0, M.cols):
- if M[M.rows - 1, i] == 0:
- j = j - 1
- else:
- return j if j >= 0 else 0
- def final_touches(s2, r, deg_g):
- """
- s2 is sylvester2, r is the row pointer in s2,
- deg_g is the degree of the poly last inserted in s2.
- After a gcd of degree > 0 has been found with Van Vleck's
- method, and was inserted into s2, if its last term is not
- in the last column of s2, then it is inserted as many
- times as needed, rotated right by one each time, until
- the condition is met.
- """
- R = s2.row(r-1)
- # find the first non zero term
- for i in range(s2.cols):
- if R[0,i] == 0:
- continue
- else:
- break
- # missing rows until last term is in last column
- mr = s2.cols - (i + deg_g + 1)
- # insert them by replacing the existing entries in the row
- i = 0
- while mr != 0 and r + i < s2.rows :
- s2[r + i, : ] = rotate_r(R, i + 1)
- i += 1
- mr -= 1
- return s2
- def subresultants_vv(p, q, x, method = 0):
- """
- p, q are polynomials in Z[x] (intended) or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the subresultant prs of p, q by triangularizing,
- in Z[x] or in Q[x], all the smaller matrices encountered in the
- process of triangularizing sylvester2, Sylvester's matrix of 1853;
- see references 1 and 2 for Van Vleck's method. With each remainder,
- sylvester2 gets updated and is prepared to be printed if requested.
- If sylvester2 has small dimensions and you want to see the final,
- triangularized matrix use this version with method=1; otherwise,
- use either this version with method=0 (default) or the faster version,
- subresultants_vv_2(p, q, x), where sylvester2 is used implicitly.
- Sylvester's matrix sylvester1 is also used to compute one
- subresultant per remainder; namely, that of the leading
- coefficient, in order to obtain the correct sign and to
- force the remainder coefficients to become subresultants.
- If the subresultant prs is complete, then it coincides with the
- Euclidean sequence of the polynomials p, q.
- If the final, triangularized matrix s2 is printed, then:
- (a) if deg(p) - deg(q) > 1 or deg( gcd(p, q) ) > 0, several
- of the last rows in s2 will remain unprocessed;
- (b) if deg(p) - deg(q) == 0, p will not appear in the final matrix.
- References
- ==========
- 1. Akritas, A. G.: ``A new method for computing polynomial greatest
- common divisors and polynomial remainder sequences.''
- Numerische MatheMatik 52, 119-127, 1988.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem
- by Van Vleck Regarding Sturm Sequences.''
- Serdica Journal of Computing, 7, No 4, 101-134, 2013.
- 3. Akritas, A. G.:``Three New Methods for Computing Subresultant
- Polynomial Remainder Sequences (PRS's).'' Serdica Journal of Computing 9(1) (2015), 1-26.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- # make sure proper degrees
- f, g = p, q
- n = deg_f = degree(f, x)
- m = deg_g = degree(g, x)
- if n == 0 and m == 0:
- return [f, g]
- if n < m:
- n, m, deg_f, deg_g, f, g = m, n, deg_g, deg_f, g, f
- if n > 0 and m == 0:
- return [f, g]
- # initialize
- s1 = sylvester(f, g, x, 1)
- s2 = sylvester(f, g, x, 2)
- sr_list = [f, g]
- col_num = 2 * n # columns in s2
- # make two rows (row0, row1) of poly coefficients
- row0 = Poly(f, x, domain = QQ).all_coeffs()
- leng0 = len(row0)
- for i in range(col_num - leng0):
- row0.append(0)
- row0 = Matrix([row0])
- row1 = Poly(g,x, domain = QQ).all_coeffs()
- leng1 = len(row1)
- for i in range(col_num - leng1):
- row1.append(0)
- row1 = Matrix([row1])
- # row pointer for deg_f - deg_g == 1; may be reset below
- r = 2
- # modify first rows of s2 matrix depending on poly degrees
- if deg_f - deg_g > 1:
- r = 1
- # replacing the existing entries in the rows of s2,
- # insert row0 (deg_f - deg_g - 1) times, rotated each time
- for i in range(deg_f - deg_g - 1):
- s2[r + i, : ] = rotate_r(row0, i + 1)
- r = r + deg_f - deg_g - 1
- # insert row1 (deg_f - deg_g) times, rotated each time
- for i in range(deg_f - deg_g):
- s2[r + i, : ] = rotate_r(row1, r + i)
- r = r + deg_f - deg_g
- if deg_f - deg_g == 0:
- r = 0
- # main loop
- while deg_g > 0:
- # create a small matrix M, and triangularize it;
- M = create_ma(deg_f, deg_g, row1, row0, col_num)
- # will need only the first and last rows of M
- for i in range(deg_f - deg_g + 1):
- M1 = pivot(M, i, i)
- M = M1[:, :]
- # treat last row of M as poly; find its degree
- d = find_degree(M, deg_f)
- if d is None:
- break
- exp_deg = deg_g - 1
- # evaluate one determinant & make coefficients subresultants
- sign_value = correct_sign(n, m, s1, exp_deg, exp_deg - d)
- poly = row2poly(M[M.rows - 1, :], d, x)
- temp2 = LC(poly, x)
- poly = simplify((poly / temp2) * sign_value)
- # update s2 by inserting first row of M as needed
- row0 = M[0, :]
- for i in range(deg_g - d):
- s2[r + i, :] = rotate_r(row0, r + i)
- r = r + deg_g - d
- # update s2 by inserting last row of M as needed
- row1 = rotate_l(M[M.rows - 1, :], deg_f - d)
- row1 = (row1 / temp2) * sign_value
- for i in range(deg_g - d):
- s2[r + i, :] = rotate_r(row1, r + i)
- r = r + deg_g - d
- # update degrees
- deg_f, deg_g = deg_g, d
- # append poly with subresultant coeffs
- sr_list.append(poly)
- # final touches to print the s2 matrix
- if method != 0 and s2.rows > 2:
- s2 = final_touches(s2, r, deg_g)
- pprint(s2)
- elif method != 0 and s2.rows == 2:
- s2[1, :] = rotate_r(s2.row(1), 1)
- pprint(s2)
- return sr_list
- def subresultants_vv_2(p, q, x):
- """
- p, q are polynomials in Z[x] (intended) or Q[x]. It is assumed
- that degree(p, x) >= degree(q, x).
- Computes the subresultant prs of p, q by triangularizing,
- in Z[x] or in Q[x], all the smaller matrices encountered in the
- process of triangularizing sylvester2, Sylvester's matrix of 1853;
- see references 1 and 2 for Van Vleck's method.
- If the sylvester2 matrix has big dimensions use this version,
- where sylvester2 is used implicitly. If you want to see the final,
- triangularized matrix sylvester2, then use the first version,
- subresultants_vv(p, q, x, 1).
- sylvester1, Sylvester's matrix of 1840, is also used to compute
- one subresultant per remainder; namely, that of the leading
- coefficient, in order to obtain the correct sign and to
- ``force'' the remainder coefficients to become subresultants.
- If the subresultant prs is complete, then it coincides with the
- Euclidean sequence of the polynomials p, q.
- References
- ==========
- 1. Akritas, A. G.: ``A new method for computing polynomial greatest
- common divisors and polynomial remainder sequences.''
- Numerische MatheMatik 52, 119-127, 1988.
- 2. Akritas, A. G., G.I. Malaschonok and P.S. Vigklas: ``On a Theorem
- by Van Vleck Regarding Sturm Sequences.''
- Serdica Journal of Computing, 7, No 4, 101-134, 2013.
- 3. Akritas, A. G.:``Three New Methods for Computing Subresultant
- Polynomial Remainder Sequences (PRS's).'' Serdica Journal of Computing 9(1) (2015), 1-26.
- """
- # make sure neither p nor q is 0
- if p == 0 or q == 0:
- return [p, q]
- # make sure proper degrees
- f, g = p, q
- n = deg_f = degree(f, x)
- m = deg_g = degree(g, x)
- if n == 0 and m == 0:
- return [f, g]
- if n < m:
- n, m, deg_f, deg_g, f, g = m, n, deg_g, deg_f, g, f
- if n > 0 and m == 0:
- return [f, g]
- # initialize
- s1 = sylvester(f, g, x, 1)
- sr_list = [f, g] # subresultant list
- col_num = 2 * n # columns in sylvester2
- # make two rows (row0, row1) of poly coefficients
- row0 = Poly(f, x, domain = QQ).all_coeffs()
- leng0 = len(row0)
- for i in range(col_num - leng0):
- row0.append(0)
- row0 = Matrix([row0])
- row1 = Poly(g,x, domain = QQ).all_coeffs()
- leng1 = len(row1)
- for i in range(col_num - leng1):
- row1.append(0)
- row1 = Matrix([row1])
- # main loop
- while deg_g > 0:
- # create a small matrix M, and triangularize it
- M = create_ma(deg_f, deg_g, row1, row0, col_num)
- for i in range(deg_f - deg_g + 1):
- M1 = pivot(M, i, i)
- M = M1[:, :]
- # treat last row of M as poly; find its degree
- d = find_degree(M, deg_f)
- if d is None:
- return sr_list
- exp_deg = deg_g - 1
- # evaluate one determinant & make coefficients subresultants
- sign_value = correct_sign(n, m, s1, exp_deg, exp_deg - d)
- poly = row2poly(M[M.rows - 1, :], d, x)
- poly = simplify((poly / LC(poly, x)) * sign_value)
- # append poly with subresultant coeffs
- sr_list.append(poly)
- # update degrees and rows
- deg_f, deg_g = deg_g, d
- row0 = row1
- row1 = Poly(poly, x, domain = QQ).all_coeffs()
- leng1 = len(row1)
- for i in range(col_num - leng1):
- row1.append(0)
- row1 = Matrix([row1])
- return sr_list
|