123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277 |
- from sympy.core.symbol import Dummy
- from sympy.ntheory import nextprime
- from sympy.ntheory.modular import crt
- from sympy.polys.domains import PolynomialRing
- from sympy.polys.galoistools import (
- gf_gcd, gf_from_dict, gf_gcdex, gf_div, gf_lcm)
- from sympy.polys.polyerrors import ModularGCDFailed
- from mpmath import sqrt
- import random
- def _trivial_gcd(f, g):
- """
- Compute the GCD of two polynomials in trivial cases, i.e. when one
- or both polynomials are zero.
- """
- ring = f.ring
- if not (f or g):
- return ring.zero, ring.zero, ring.zero
- elif not f:
- if g.LC < ring.domain.zero:
- return -g, ring.zero, -ring.one
- else:
- return g, ring.zero, ring.one
- elif not g:
- if f.LC < ring.domain.zero:
- return -f, -ring.one, ring.zero
- else:
- return f, ring.one, ring.zero
- return None
- def _gf_gcd(fp, gp, p):
- r"""
- Compute the GCD of two univariate polynomials in `\mathbb{Z}_p[x]`.
- """
- dom = fp.ring.domain
- while gp:
- rem = fp
- deg = gp.degree()
- lcinv = dom.invert(gp.LC, p)
- while True:
- degrem = rem.degree()
- if degrem < deg:
- break
- rem = (rem - gp.mul_monom((degrem - deg,)).mul_ground(lcinv * rem.LC)).trunc_ground(p)
- fp = gp
- gp = rem
- return fp.mul_ground(dom.invert(fp.LC, p)).trunc_ground(p)
- def _degree_bound_univariate(f, g):
- r"""
- Compute an upper bound for the degree of the GCD of two univariate
- integer polynomials `f` and `g`.
- The function chooses a suitable prime `p` and computes the GCD of
- `f` and `g` in `\mathbb{Z}_p[x]`. The choice of `p` guarantees that
- the degree in `\mathbb{Z}_p[x]` is greater than or equal to the degree
- in `\mathbb{Z}[x]`.
- Parameters
- ==========
- f : PolyElement
- univariate integer polynomial
- g : PolyElement
- univariate integer polynomial
- """
- gamma = f.ring.domain.gcd(f.LC, g.LC)
- p = 1
- p = nextprime(p)
- while gamma % p == 0:
- p = nextprime(p)
- fp = f.trunc_ground(p)
- gp = g.trunc_ground(p)
- hp = _gf_gcd(fp, gp, p)
- deghp = hp.degree()
- return deghp
- def _chinese_remainder_reconstruction_univariate(hp, hq, p, q):
- r"""
- Construct a polynomial `h_{pq}` in `\mathbb{Z}_{p q}[x]` such that
- .. math ::
- h_{pq} = h_p \; \mathrm{mod} \, p
- h_{pq} = h_q \; \mathrm{mod} \, q
- for relatively prime integers `p` and `q` and polynomials
- `h_p` and `h_q` in `\mathbb{Z}_p[x]` and `\mathbb{Z}_q[x]`
- respectively.
- The coefficients of the polynomial `h_{pq}` are computed with the
- Chinese Remainder Theorem. The symmetric representation in
- `\mathbb{Z}_p[x]`, `\mathbb{Z}_q[x]` and `\mathbb{Z}_{p q}[x]` is used.
- It is assumed that `h_p` and `h_q` have the same degree.
- Parameters
- ==========
- hp : PolyElement
- univariate integer polynomial with coefficients in `\mathbb{Z}_p`
- hq : PolyElement
- univariate integer polynomial with coefficients in `\mathbb{Z}_q`
- p : Integer
- modulus of `h_p`, relatively prime to `q`
- q : Integer
- modulus of `h_q`, relatively prime to `p`
- Examples
- ========
- >>> from sympy.polys.modulargcd import _chinese_remainder_reconstruction_univariate
- >>> from sympy.polys import ring, ZZ
- >>> R, x = ring("x", ZZ)
- >>> p = 3
- >>> q = 5
- >>> hp = -x**3 - 1
- >>> hq = 2*x**3 - 2*x**2 + x
- >>> hpq = _chinese_remainder_reconstruction_univariate(hp, hq, p, q)
- >>> hpq
- 2*x**3 + 3*x**2 + 6*x + 5
- >>> hpq.trunc_ground(p) == hp
- True
- >>> hpq.trunc_ground(q) == hq
- True
- """
- n = hp.degree()
- x = hp.ring.gens[0]
- hpq = hp.ring.zero
- for i in range(n+1):
- hpq[(i,)] = crt([p, q], [hp.coeff(x**i), hq.coeff(x**i)], symmetric=True)[0]
- hpq.strip_zero()
- return hpq
- def modgcd_univariate(f, g):
- r"""
- Computes the GCD of two polynomials in `\mathbb{Z}[x]` using a modular
- algorithm.
- The algorithm computes the GCD of two univariate integer polynomials
- `f` and `g` by computing the GCD in `\mathbb{Z}_p[x]` for suitable
- primes `p` and then reconstructing the coefficients with the Chinese
- Remainder Theorem. Trial division is only made for candidates which
- are very likely the desired GCD.
- Parameters
- ==========
- f : PolyElement
- univariate integer polynomial
- g : PolyElement
- univariate integer polynomial
- Returns
- =======
- h : PolyElement
- GCD of the polynomials `f` and `g`
- cff : PolyElement
- cofactor of `f`, i.e. `\frac{f}{h}`
- cfg : PolyElement
- cofactor of `g`, i.e. `\frac{g}{h}`
- Examples
- ========
- >>> from sympy.polys.modulargcd import modgcd_univariate
- >>> from sympy.polys import ring, ZZ
- >>> R, x = ring("x", ZZ)
- >>> f = x**5 - 1
- >>> g = x - 1
- >>> h, cff, cfg = modgcd_univariate(f, g)
- >>> h, cff, cfg
- (x - 1, x**4 + x**3 + x**2 + x + 1, 1)
- >>> cff * h == f
- True
- >>> cfg * h == g
- True
- >>> f = 6*x**2 - 6
- >>> g = 2*x**2 + 4*x + 2
- >>> h, cff, cfg = modgcd_univariate(f, g)
- >>> h, cff, cfg
- (2*x + 2, 3*x - 3, x + 1)
- >>> cff * h == f
- True
- >>> cfg * h == g
- True
- References
- ==========
- 1. [Monagan00]_
- """
- assert f.ring == g.ring and f.ring.domain.is_ZZ
- result = _trivial_gcd(f, g)
- if result is not None:
- return result
- ring = f.ring
- cf, f = f.primitive()
- cg, g = g.primitive()
- ch = ring.domain.gcd(cf, cg)
- bound = _degree_bound_univariate(f, g)
- if bound == 0:
- return ring(ch), f.mul_ground(cf // ch), g.mul_ground(cg // ch)
- gamma = ring.domain.gcd(f.LC, g.LC)
- m = 1
- p = 1
- while True:
- p = nextprime(p)
- while gamma % p == 0:
- p = nextprime(p)
- fp = f.trunc_ground(p)
- gp = g.trunc_ground(p)
- hp = _gf_gcd(fp, gp, p)
- deghp = hp.degree()
- if deghp > bound:
- continue
- elif deghp < bound:
- m = 1
- bound = deghp
- continue
- hp = hp.mul_ground(gamma).trunc_ground(p)
- if m == 1:
- m = p
- hlastm = hp
- continue
- hm = _chinese_remainder_reconstruction_univariate(hp, hlastm, p, m)
- m *= p
- if not hm == hlastm:
- hlastm = hm
- continue
- h = hm.quo_ground(hm.content())
- fquo, frem = f.div(h)
- gquo, grem = g.div(h)
- if not frem and not grem:
- if h.LC < 0:
- ch = -ch
- h = h.mul_ground(ch)
- cff = fquo.mul_ground(cf // ch)
- cfg = gquo.mul_ground(cg // ch)
- return h, cff, cfg
- def _primitive(f, p):
- r"""
- Compute the content and the primitive part of a polynomial in
- `\mathbb{Z}_p[x_0, \ldots, x_{k-2}, y] \cong \mathbb{Z}_p[y][x_0, \ldots, x_{k-2}]`.
- Parameters
- ==========
- f : PolyElement
- integer polynomial in `\mathbb{Z}_p[x0, \ldots, x{k-2}, y]`
- p : Integer
- modulus of `f`
- Returns
- =======
- contf : PolyElement
- integer polynomial in `\mathbb{Z}_p[y]`, content of `f`
- ppf : PolyElement
- primitive part of `f`, i.e. `\frac{f}{contf}`
- Examples
- ========
- >>> from sympy.polys.modulargcd import _primitive
- >>> from sympy.polys import ring, ZZ
- >>> R, x, y = ring("x, y", ZZ)
- >>> p = 3
- >>> f = x**2*y**2 + x**2*y - y**2 - y
- >>> _primitive(f, p)
- (y**2 + y, x**2 - 1)
- >>> R, x, y, z = ring("x, y, z", ZZ)
- >>> f = x*y*z - y**2*z**2
- >>> _primitive(f, p)
- (z, x*y - y**2*z)
- """
- ring = f.ring
- dom = ring.domain
- k = ring.ngens
- coeffs = {}
- for monom, coeff in f.iterterms():
- if monom[:-1] not in coeffs:
- coeffs[monom[:-1]] = {}
- coeffs[monom[:-1]][monom[-1]] = coeff
- cont = []
- for coeff in iter(coeffs.values()):
- cont = gf_gcd(cont, gf_from_dict(coeff, p, dom), p, dom)
- yring = ring.clone(symbols=ring.symbols[k-1])
- contf = yring.from_dense(cont).trunc_ground(p)
- return contf, f.quo(contf.set_ring(ring))
- def _deg(f):
- r"""
- Compute the degree of a multivariate polynomial
- `f \in K[x_0, \ldots, x_{k-2}, y] \cong K[y][x_0, \ldots, x_{k-2}]`.
- Parameters
- ==========
- f : PolyElement
- polynomial in `K[x_0, \ldots, x_{k-2}, y]`
- Returns
- =======
- degf : Integer tuple
- degree of `f` in `x_0, \ldots, x_{k-2}`
- Examples
- ========
- >>> from sympy.polys.modulargcd import _deg
- >>> from sympy.polys import ring, ZZ
- >>> R, x, y = ring("x, y", ZZ)
- >>> f = x**2*y**2 + x**2*y - 1
- >>> _deg(f)
- (2,)
- >>> R, x, y, z = ring("x, y, z", ZZ)
- >>> f = x**2*y**2 + x**2*y - 1
- >>> _deg(f)
- (2, 2)
- >>> f = x*y*z - y**2*z**2
- >>> _deg(f)
- (1, 1)
- """
- k = f.ring.ngens
- degf = (0,) * (k-1)
- for monom in f.itermonoms():
- if monom[:-1] > degf:
- degf = monom[:-1]
- return degf
- def _LC(f):
- r"""
- Compute the leading coefficient of a multivariate polynomial
- `f \in K[x_0, \ldots, x_{k-2}, y] \cong K[y][x_0, \ldots, x_{k-2}]`.
- Parameters
- ==========
- f : PolyElement
- polynomial in `K[x_0, \ldots, x_{k-2}, y]`
- Returns
- =======
- lcf : PolyElement
- polynomial in `K[y]`, leading coefficient of `f`
- Examples
- ========
- >>> from sympy.polys.modulargcd import _LC
- >>> from sympy.polys import ring, ZZ
- >>> R, x, y = ring("x, y", ZZ)
- >>> f = x**2*y**2 + x**2*y - 1
- >>> _LC(f)
- y**2 + y
- >>> R, x, y, z = ring("x, y, z", ZZ)
- >>> f = x**2*y**2 + x**2*y - 1
- >>> _LC(f)
- 1
- >>> f = x*y*z - y**2*z**2
- >>> _LC(f)
- z
- """
- ring = f.ring
- k = ring.ngens
- yring = ring.clone(symbols=ring.symbols[k-1])
- y = yring.gens[0]
- degf = _deg(f)
- lcf = yring.zero
- for monom, coeff in f.iterterms():
- if monom[:-1] == degf:
- lcf += coeff*y**monom[-1]
- return lcf
- def _swap(f, i):
- """
- Make the variable `x_i` the leading one in a multivariate polynomial `f`.
- """
- ring = f.ring
- fswap = ring.zero
- for monom, coeff in f.iterterms():
- monomswap = (monom[i],) + monom[:i] + monom[i+1:]
- fswap[monomswap] = coeff
- return fswap
- def _degree_bound_bivariate(f, g):
- r"""
- Compute upper degree bounds for the GCD of two bivariate
- integer polynomials `f` and `g`.
- The GCD is viewed as a polynomial in `\mathbb{Z}[y][x]` and the
- function returns an upper bound for its degree and one for the degree
- of its content. This is done by choosing a suitable prime `p` and
- computing the GCD of the contents of `f \; \mathrm{mod} \, p` and
- `g \; \mathrm{mod} \, p`. The choice of `p` guarantees that the degree
- of the content in `\mathbb{Z}_p[y]` is greater than or equal to the
- degree in `\mathbb{Z}[y]`. To obtain the degree bound in the variable
- `x`, the polynomials are evaluated at `y = a` for a suitable
- `a \in \mathbb{Z}_p` and then their GCD in `\mathbb{Z}_p[x]` is
- computed. If no such `a` exists, i.e. the degree in `\mathbb{Z}_p[x]`
- is always smaller than the one in `\mathbb{Z}[y][x]`, then the bound is
- set to the minimum of the degrees of `f` and `g` in `x`.
- Parameters
- ==========
- f : PolyElement
- bivariate integer polynomial
- g : PolyElement
- bivariate integer polynomial
- Returns
- =======
- xbound : Integer
- upper bound for the degree of the GCD of the polynomials `f` and
- `g` in the variable `x`
- ycontbound : Integer
- upper bound for the degree of the content of the GCD of the
- polynomials `f` and `g` in the variable `y`
- References
- ==========
- 1. [Monagan00]_
- """
- ring = f.ring
- gamma1 = ring.domain.gcd(f.LC, g.LC)
- gamma2 = ring.domain.gcd(_swap(f, 1).LC, _swap(g, 1).LC)
- badprimes = gamma1 * gamma2
- p = 1
- p = nextprime(p)
- while badprimes % p == 0:
- p = nextprime(p)
- fp = f.trunc_ground(p)
- gp = g.trunc_ground(p)
- contfp, fp = _primitive(fp, p)
- contgp, gp = _primitive(gp, p)
- conthp = _gf_gcd(contfp, contgp, p) # polynomial in Z_p[y]
- ycontbound = conthp.degree()
- # polynomial in Z_p[y]
- delta = _gf_gcd(_LC(fp), _LC(gp), p)
- for a in range(p):
- if not delta.evaluate(0, a) % p:
- continue
- fpa = fp.evaluate(1, a).trunc_ground(p)
- gpa = gp.evaluate(1, a).trunc_ground(p)
- hpa = _gf_gcd(fpa, gpa, p)
- xbound = hpa.degree()
- return xbound, ycontbound
- return min(fp.degree(), gp.degree()), ycontbound
- def _chinese_remainder_reconstruction_multivariate(hp, hq, p, q):
- r"""
- Construct a polynomial `h_{pq}` in
- `\mathbb{Z}_{p q}[x_0, \ldots, x_{k-1}]` such that
- .. math ::
- h_{pq} = h_p \; \mathrm{mod} \, p
- h_{pq} = h_q \; \mathrm{mod} \, q
- for relatively prime integers `p` and `q` and polynomials
- `h_p` and `h_q` in `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]` and
- `\mathbb{Z}_q[x_0, \ldots, x_{k-1}]` respectively.
- The coefficients of the polynomial `h_{pq}` are computed with the
- Chinese Remainder Theorem. The symmetric representation in
- `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`,
- `\mathbb{Z}_q[x_0, \ldots, x_{k-1}]` and
- `\mathbb{Z}_{p q}[x_0, \ldots, x_{k-1}]` is used.
- Parameters
- ==========
- hp : PolyElement
- multivariate integer polynomial with coefficients in `\mathbb{Z}_p`
- hq : PolyElement
- multivariate integer polynomial with coefficients in `\mathbb{Z}_q`
- p : Integer
- modulus of `h_p`, relatively prime to `q`
- q : Integer
- modulus of `h_q`, relatively prime to `p`
- Examples
- ========
- >>> from sympy.polys.modulargcd import _chinese_remainder_reconstruction_multivariate
- >>> from sympy.polys import ring, ZZ
- >>> R, x, y = ring("x, y", ZZ)
- >>> p = 3
- >>> q = 5
- >>> hp = x**3*y - x**2 - 1
- >>> hq = -x**3*y - 2*x*y**2 + 2
- >>> hpq = _chinese_remainder_reconstruction_multivariate(hp, hq, p, q)
- >>> hpq
- 4*x**3*y + 5*x**2 + 3*x*y**2 + 2
- >>> hpq.trunc_ground(p) == hp
- True
- >>> hpq.trunc_ground(q) == hq
- True
- >>> R, x, y, z = ring("x, y, z", ZZ)
- >>> p = 6
- >>> q = 5
- >>> hp = 3*x**4 - y**3*z + z
- >>> hq = -2*x**4 + z
- >>> hpq = _chinese_remainder_reconstruction_multivariate(hp, hq, p, q)
- >>> hpq
- 3*x**4 + 5*y**3*z + z
- >>> hpq.trunc_ground(p) == hp
- True
- >>> hpq.trunc_ground(q) == hq
- True
- """
- hpmonoms = set(hp.monoms())
- hqmonoms = set(hq.monoms())
- monoms = hpmonoms.intersection(hqmonoms)
- hpmonoms.difference_update(monoms)
- hqmonoms.difference_update(monoms)
- zero = hp.ring.domain.zero
- hpq = hp.ring.zero
- if isinstance(hp.ring.domain, PolynomialRing):
- crt_ = _chinese_remainder_reconstruction_multivariate
- else:
- def crt_(cp, cq, p, q):
- return crt([p, q], [cp, cq], symmetric=True)[0]
- for monom in monoms:
- hpq[monom] = crt_(hp[monom], hq[monom], p, q)
- for monom in hpmonoms:
- hpq[monom] = crt_(hp[monom], zero, p, q)
- for monom in hqmonoms:
- hpq[monom] = crt_(zero, hq[monom], p, q)
- return hpq
- def _interpolate_multivariate(evalpoints, hpeval, ring, i, p, ground=False):
- r"""
- Reconstruct a polynomial `h_p` in `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`
- from a list of evaluation points in `\mathbb{Z}_p` and a list of
- polynomials in
- `\mathbb{Z}_p[x_0, \ldots, x_{i-1}, x_{i+1}, \ldots, x_{k-1}]`, which
- are the images of `h_p` evaluated in the variable `x_i`.
- It is also possible to reconstruct a parameter of the ground domain,
- i.e. if `h_p` is a polynomial over `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`.
- In this case, one has to set ``ground=True``.
- Parameters
- ==========
- evalpoints : list of Integer objects
- list of evaluation points in `\mathbb{Z}_p`
- hpeval : list of PolyElement objects
- list of polynomials in (resp. over)
- `\mathbb{Z}_p[x_0, \ldots, x_{i-1}, x_{i+1}, \ldots, x_{k-1}]`,
- images of `h_p` evaluated in the variable `x_i`
- ring : PolyRing
- `h_p` will be an element of this ring
- i : Integer
- index of the variable which has to be reconstructed
- p : Integer
- prime number, modulus of `h_p`
- ground : Boolean
- indicates whether `x_i` is in the ground domain, default is
- ``False``
- Returns
- =======
- hp : PolyElement
- interpolated polynomial in (resp. over)
- `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`
- """
- hp = ring.zero
- if ground:
- domain = ring.domain.domain
- y = ring.domain.gens[i]
- else:
- domain = ring.domain
- y = ring.gens[i]
- for a, hpa in zip(evalpoints, hpeval):
- numer = ring.one
- denom = domain.one
- for b in evalpoints:
- if b == a:
- continue
- numer *= y - b
- denom *= a - b
- denom = domain.invert(denom, p)
- coeff = numer.mul_ground(denom)
- hp += hpa.set_ring(ring) * coeff
- return hp.trunc_ground(p)
- def modgcd_bivariate(f, g):
- r"""
- Computes the GCD of two polynomials in `\mathbb{Z}[x, y]` using a
- modular algorithm.
- The algorithm computes the GCD of two bivariate integer polynomials
- `f` and `g` by calculating the GCD in `\mathbb{Z}_p[x, y]` for
- suitable primes `p` and then reconstructing the coefficients with the
- Chinese Remainder Theorem. To compute the bivariate GCD over
- `\mathbb{Z}_p`, the polynomials `f \; \mathrm{mod} \, p` and
- `g \; \mathrm{mod} \, p` are evaluated at `y = a` for certain
- `a \in \mathbb{Z}_p` and then their univariate GCD in `\mathbb{Z}_p[x]`
- is computed. Interpolating those yields the bivariate GCD in
- `\mathbb{Z}_p[x, y]`. To verify the result in `\mathbb{Z}[x, y]`, trial
- division is done, but only for candidates which are very likely the
- desired GCD.
- Parameters
- ==========
- f : PolyElement
- bivariate integer polynomial
- g : PolyElement
- bivariate integer polynomial
- Returns
- =======
- h : PolyElement
- GCD of the polynomials `f` and `g`
- cff : PolyElement
- cofactor of `f`, i.e. `\frac{f}{h}`
- cfg : PolyElement
- cofactor of `g`, i.e. `\frac{g}{h}`
- Examples
- ========
- >>> from sympy.polys.modulargcd import modgcd_bivariate
- >>> from sympy.polys import ring, ZZ
- >>> R, x, y = ring("x, y", ZZ)
- >>> f = x**2 - y**2
- >>> g = x**2 + 2*x*y + y**2
- >>> h, cff, cfg = modgcd_bivariate(f, g)
- >>> h, cff, cfg
- (x + y, x - y, x + y)
- >>> cff * h == f
- True
- >>> cfg * h == g
- True
- >>> f = x**2*y - x**2 - 4*y + 4
- >>> g = x + 2
- >>> h, cff, cfg = modgcd_bivariate(f, g)
- >>> h, cff, cfg
- (x + 2, x*y - x - 2*y + 2, 1)
- >>> cff * h == f
- True
- >>> cfg * h == g
- True
- References
- ==========
- 1. [Monagan00]_
- """
- assert f.ring == g.ring and f.ring.domain.is_ZZ
- result = _trivial_gcd(f, g)
- if result is not None:
- return result
- ring = f.ring
- cf, f = f.primitive()
- cg, g = g.primitive()
- ch = ring.domain.gcd(cf, cg)
- xbound, ycontbound = _degree_bound_bivariate(f, g)
- if xbound == ycontbound == 0:
- return ring(ch), f.mul_ground(cf // ch), g.mul_ground(cg // ch)
- fswap = _swap(f, 1)
- gswap = _swap(g, 1)
- degyf = fswap.degree()
- degyg = gswap.degree()
- ybound, xcontbound = _degree_bound_bivariate(fswap, gswap)
- if ybound == xcontbound == 0:
- return ring(ch), f.mul_ground(cf // ch), g.mul_ground(cg // ch)
- # TODO: to improve performance, choose the main variable here
- gamma1 = ring.domain.gcd(f.LC, g.LC)
- gamma2 = ring.domain.gcd(fswap.LC, gswap.LC)
- badprimes = gamma1 * gamma2
- m = 1
- p = 1
- while True:
- p = nextprime(p)
- while badprimes % p == 0:
- p = nextprime(p)
- fp = f.trunc_ground(p)
- gp = g.trunc_ground(p)
- contfp, fp = _primitive(fp, p)
- contgp, gp = _primitive(gp, p)
- conthp = _gf_gcd(contfp, contgp, p) # monic polynomial in Z_p[y]
- degconthp = conthp.degree()
- if degconthp > ycontbound:
- continue
- elif degconthp < ycontbound:
- m = 1
- ycontbound = degconthp
- continue
- # polynomial in Z_p[y]
- delta = _gf_gcd(_LC(fp), _LC(gp), p)
- degcontfp = contfp.degree()
- degcontgp = contgp.degree()
- degdelta = delta.degree()
- N = min(degyf - degcontfp, degyg - degcontgp,
- ybound - ycontbound + degdelta) + 1
- if p < N:
- continue
- n = 0
- evalpoints = []
- hpeval = []
- unlucky = False
- for a in range(p):
- deltaa = delta.evaluate(0, a)
- if not deltaa % p:
- continue
- fpa = fp.evaluate(1, a).trunc_ground(p)
- gpa = gp.evaluate(1, a).trunc_ground(p)
- hpa = _gf_gcd(fpa, gpa, p) # monic polynomial in Z_p[x]
- deghpa = hpa.degree()
- if deghpa > xbound:
- continue
- elif deghpa < xbound:
- m = 1
- xbound = deghpa
- unlucky = True
- break
- hpa = hpa.mul_ground(deltaa).trunc_ground(p)
- evalpoints.append(a)
- hpeval.append(hpa)
- n += 1
- if n == N:
- break
- if unlucky:
- continue
- if n < N:
- continue
- hp = _interpolate_multivariate(evalpoints, hpeval, ring, 1, p)
- hp = _primitive(hp, p)[1]
- hp = hp * conthp.set_ring(ring)
- degyhp = hp.degree(1)
- if degyhp > ybound:
- continue
- if degyhp < ybound:
- m = 1
- ybound = degyhp
- continue
- hp = hp.mul_ground(gamma1).trunc_ground(p)
- if m == 1:
- m = p
- hlastm = hp
- continue
- hm = _chinese_remainder_reconstruction_multivariate(hp, hlastm, p, m)
- m *= p
- if not hm == hlastm:
- hlastm = hm
- continue
- h = hm.quo_ground(hm.content())
- fquo, frem = f.div(h)
- gquo, grem = g.div(h)
- if not frem and not grem:
- if h.LC < 0:
- ch = -ch
- h = h.mul_ground(ch)
- cff = fquo.mul_ground(cf // ch)
- cfg = gquo.mul_ground(cg // ch)
- return h, cff, cfg
- def _modgcd_multivariate_p(f, g, p, degbound, contbound):
- r"""
- Compute the GCD of two polynomials in
- `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]`.
- The algorithm reduces the problem step by step by evaluating the
- polynomials `f` and `g` at `x_{k-1} = a` for suitable
- `a \in \mathbb{Z}_p` and then calls itself recursively to compute the GCD
- in `\mathbb{Z}_p[x_0, \ldots, x_{k-2}]`. If these recursive calls are
- successful for enough evaluation points, the GCD in `k` variables is
- interpolated, otherwise the algorithm returns ``None``. Every time a GCD
- or a content is computed, their degrees are compared with the bounds. If
- a degree greater then the bound is encountered, then the current call
- returns ``None`` and a new evaluation point has to be chosen. If at some
- point the degree is smaller, the correspondent bound is updated and the
- algorithm fails.
- Parameters
- ==========
- f : PolyElement
- multivariate integer polynomial with coefficients in `\mathbb{Z}_p`
- g : PolyElement
- multivariate integer polynomial with coefficients in `\mathbb{Z}_p`
- p : Integer
- prime number, modulus of `f` and `g`
- degbound : list of Integer objects
- ``degbound[i]`` is an upper bound for the degree of the GCD of `f`
- and `g` in the variable `x_i`
- contbound : list of Integer objects
- ``contbound[i]`` is an upper bound for the degree of the content of
- the GCD in `\mathbb{Z}_p[x_i][x_0, \ldots, x_{i-1}]`,
- ``contbound[0]`` is not used can therefore be chosen
- arbitrarily.
- Returns
- =======
- h : PolyElement
- GCD of the polynomials `f` and `g` or ``None``
- References
- ==========
- 1. [Monagan00]_
- 2. [Brown71]_
- """
- ring = f.ring
- k = ring.ngens
- if k == 1:
- h = _gf_gcd(f, g, p).trunc_ground(p)
- degh = h.degree()
- if degh > degbound[0]:
- return None
- if degh < degbound[0]:
- degbound[0] = degh
- raise ModularGCDFailed
- return h
- degyf = f.degree(k-1)
- degyg = g.degree(k-1)
- contf, f = _primitive(f, p)
- contg, g = _primitive(g, p)
- conth = _gf_gcd(contf, contg, p) # polynomial in Z_p[y]
- degcontf = contf.degree()
- degcontg = contg.degree()
- degconth = conth.degree()
- if degconth > contbound[k-1]:
- return None
- if degconth < contbound[k-1]:
- contbound[k-1] = degconth
- raise ModularGCDFailed
- lcf = _LC(f)
- lcg = _LC(g)
- delta = _gf_gcd(lcf, lcg, p) # polynomial in Z_p[y]
- evaltest = delta
- for i in range(k-1):
- evaltest *= _gf_gcd(_LC(_swap(f, i)), _LC(_swap(g, i)), p)
- degdelta = delta.degree()
- N = min(degyf - degcontf, degyg - degcontg,
- degbound[k-1] - contbound[k-1] + degdelta) + 1
- if p < N:
- return None
- n = 0
- d = 0
- evalpoints = []
- heval = []
- points = list(range(p))
- while points:
- a = random.sample(points, 1)[0]
- points.remove(a)
- if not evaltest.evaluate(0, a) % p:
- continue
- deltaa = delta.evaluate(0, a) % p
- fa = f.evaluate(k-1, a).trunc_ground(p)
- ga = g.evaluate(k-1, a).trunc_ground(p)
- # polynomials in Z_p[x_0, ..., x_{k-2}]
- ha = _modgcd_multivariate_p(fa, ga, p, degbound, contbound)
- if ha is None:
- d += 1
- if d > n:
- return None
- continue
- if ha.is_ground:
- h = conth.set_ring(ring).trunc_ground(p)
- return h
- ha = ha.mul_ground(deltaa).trunc_ground(p)
- evalpoints.append(a)
- heval.append(ha)
- n += 1
- if n == N:
- h = _interpolate_multivariate(evalpoints, heval, ring, k-1, p)
- h = _primitive(h, p)[1] * conth.set_ring(ring)
- degyh = h.degree(k-1)
- if degyh > degbound[k-1]:
- return None
- if degyh < degbound[k-1]:
- degbound[k-1] = degyh
- raise ModularGCDFailed
- return h
- return None
- def modgcd_multivariate(f, g):
- r"""
- Compute the GCD of two polynomials in `\mathbb{Z}[x_0, \ldots, x_{k-1}]`
- using a modular algorithm.
- The algorithm computes the GCD of two multivariate integer polynomials
- `f` and `g` by calculating the GCD in
- `\mathbb{Z}_p[x_0, \ldots, x_{k-1}]` for suitable primes `p` and then
- reconstructing the coefficients with the Chinese Remainder Theorem. To
- compute the multivariate GCD over `\mathbb{Z}_p` the recursive
- subroutine :func:`_modgcd_multivariate_p` is used. To verify the result in
- `\mathbb{Z}[x_0, \ldots, x_{k-1}]`, trial division is done, but only for
- candidates which are very likely the desired GCD.
- Parameters
- ==========
- f : PolyElement
- multivariate integer polynomial
- g : PolyElement
- multivariate integer polynomial
- Returns
- =======
- h : PolyElement
- GCD of the polynomials `f` and `g`
- cff : PolyElement
- cofactor of `f`, i.e. `\frac{f}{h}`
- cfg : PolyElement
- cofactor of `g`, i.e. `\frac{g}{h}`
- Examples
- ========
- >>> from sympy.polys.modulargcd import modgcd_multivariate
- >>> from sympy.polys import ring, ZZ
- >>> R, x, y = ring("x, y", ZZ)
- >>> f = x**2 - y**2
- >>> g = x**2 + 2*x*y + y**2
- >>> h, cff, cfg = modgcd_multivariate(f, g)
- >>> h, cff, cfg
- (x + y, x - y, x + y)
- >>> cff * h == f
- True
- >>> cfg * h == g
- True
- >>> R, x, y, z = ring("x, y, z", ZZ)
- >>> f = x*z**2 - y*z**2
- >>> g = x**2*z + z
- >>> h, cff, cfg = modgcd_multivariate(f, g)
- >>> h, cff, cfg
- (z, x*z - y*z, x**2 + 1)
- >>> cff * h == f
- True
- >>> cfg * h == g
- True
- References
- ==========
- 1. [Monagan00]_
- 2. [Brown71]_
- See also
- ========
- _modgcd_multivariate_p
- """
- assert f.ring == g.ring and f.ring.domain.is_ZZ
- result = _trivial_gcd(f, g)
- if result is not None:
- return result
- ring = f.ring
- k = ring.ngens
- # divide out integer content
- cf, f = f.primitive()
- cg, g = g.primitive()
- ch = ring.domain.gcd(cf, cg)
- gamma = ring.domain.gcd(f.LC, g.LC)
- badprimes = ring.domain.one
- for i in range(k):
- badprimes *= ring.domain.gcd(_swap(f, i).LC, _swap(g, i).LC)
- degbound = [min(fdeg, gdeg) for fdeg, gdeg in zip(f.degrees(), g.degrees())]
- contbound = list(degbound)
- m = 1
- p = 1
- while True:
- p = nextprime(p)
- while badprimes % p == 0:
- p = nextprime(p)
- fp = f.trunc_ground(p)
- gp = g.trunc_ground(p)
- try:
- # monic GCD of fp, gp in Z_p[x_0, ..., x_{k-2}, y]
- hp = _modgcd_multivariate_p(fp, gp, p, degbound, contbound)
- except ModularGCDFailed:
- m = 1
- continue
- if hp is None:
- continue
- hp = hp.mul_ground(gamma).trunc_ground(p)
- if m == 1:
- m = p
- hlastm = hp
- continue
- hm = _chinese_remainder_reconstruction_multivariate(hp, hlastm, p, m)
- m *= p
- if not hm == hlastm:
- hlastm = hm
- continue
- h = hm.primitive()[1]
- fquo, frem = f.div(h)
- gquo, grem = g.div(h)
- if not frem and not grem:
- if h.LC < 0:
- ch = -ch
- h = h.mul_ground(ch)
- cff = fquo.mul_ground(cf // ch)
- cfg = gquo.mul_ground(cg // ch)
- return h, cff, cfg
- def _gf_div(f, g, p):
- r"""
- Compute `\frac f g` modulo `p` for two univariate polynomials over
- `\mathbb Z_p`.
- """
- ring = f.ring
- densequo, denserem = gf_div(f.to_dense(), g.to_dense(), p, ring.domain)
- return ring.from_dense(densequo), ring.from_dense(denserem)
- def _rational_function_reconstruction(c, p, m):
- r"""
- Reconstruct a rational function `\frac a b` in `\mathbb Z_p(t)` from
- .. math::
- c = \frac a b \; \mathrm{mod} \, m,
- where `c` and `m` are polynomials in `\mathbb Z_p[t]` and `m` has
- positive degree.
- The algorithm is based on the Euclidean Algorithm. In general, `m` is
- not irreducible, so it is possible that `b` is not invertible modulo
- `m`. In that case ``None`` is returned.
- Parameters
- ==========
- c : PolyElement
- univariate polynomial in `\mathbb Z[t]`
- p : Integer
- prime number
- m : PolyElement
- modulus, not necessarily irreducible
- Returns
- =======
- frac : FracElement
- either `\frac a b` in `\mathbb Z(t)` or ``None``
- References
- ==========
- 1. [Hoeij04]_
- """
- ring = c.ring
- domain = ring.domain
- M = m.degree()
- N = M // 2
- D = M - N - 1
- r0, s0 = m, ring.zero
- r1, s1 = c, ring.one
- while r1.degree() > N:
- quo = _gf_div(r0, r1, p)[0]
- r0, r1 = r1, (r0 - quo*r1).trunc_ground(p)
- s0, s1 = s1, (s0 - quo*s1).trunc_ground(p)
- a, b = r1, s1
- if b.degree() > D or _gf_gcd(b, m, p) != 1:
- return None
- lc = b.LC
- if lc != 1:
- lcinv = domain.invert(lc, p)
- a = a.mul_ground(lcinv).trunc_ground(p)
- b = b.mul_ground(lcinv).trunc_ground(p)
- field = ring.to_field()
- return field(a) / field(b)
- def _rational_reconstruction_func_coeffs(hm, p, m, ring, k):
- r"""
- Reconstruct every coefficient `c_h` of a polynomial `h` in
- `\mathbb Z_p(t_k)[t_1, \ldots, t_{k-1}][x, z]` from the corresponding
- coefficient `c_{h_m}` of a polynomial `h_m` in
- `\mathbb Z_p[t_1, \ldots, t_k][x, z] \cong \mathbb Z_p[t_k][t_1, \ldots, t_{k-1}][x, z]`
- such that
- .. math::
- c_{h_m} = c_h \; \mathrm{mod} \, m,
- where `m \in \mathbb Z_p[t]`.
- The reconstruction is based on the Euclidean Algorithm. In general, `m`
- is not irreducible, so it is possible that this fails for some
- coefficient. In that case ``None`` is returned.
- Parameters
- ==========
- hm : PolyElement
- polynomial in `\mathbb Z[t_1, \ldots, t_k][x, z]`
- p : Integer
- prime number, modulus of `\mathbb Z_p`
- m : PolyElement
- modulus, polynomial in `\mathbb Z[t]`, not necessarily irreducible
- ring : PolyRing
- `\mathbb Z(t_k)[t_1, \ldots, t_{k-1}][x, z]`, `h` will be an
- element of this ring
- k : Integer
- index of the parameter `t_k` which will be reconstructed
- Returns
- =======
- h : PolyElement
- reconstructed polynomial in
- `\mathbb Z(t_k)[t_1, \ldots, t_{k-1}][x, z]` or ``None``
- See also
- ========
- _rational_function_reconstruction
- """
- h = ring.zero
- for monom, coeff in hm.iterterms():
- if k == 0:
- coeffh = _rational_function_reconstruction(coeff, p, m)
- if not coeffh:
- return None
- else:
- coeffh = ring.domain.zero
- for mon, c in coeff.drop_to_ground(k).iterterms():
- ch = _rational_function_reconstruction(c, p, m)
- if not ch:
- return None
- coeffh[mon] = ch
- h[monom] = coeffh
- return h
- def _gf_gcdex(f, g, p):
- r"""
- Extended Euclidean Algorithm for two univariate polynomials over
- `\mathbb Z_p`.
- Returns polynomials `s, t` and `h`, such that `h` is the GCD of `f` and
- `g` and `sf + tg = h \; \mathrm{mod} \, p`.
- """
- ring = f.ring
- s, t, h = gf_gcdex(f.to_dense(), g.to_dense(), p, ring.domain)
- return ring.from_dense(s), ring.from_dense(t), ring.from_dense(h)
- def _trunc(f, minpoly, p):
- r"""
- Compute the reduced representation of a polynomial `f` in
- `\mathbb Z_p[z] / (\check m_{\alpha}(z))[x]`
- Parameters
- ==========
- f : PolyElement
- polynomial in `\mathbb Z[x, z]`
- minpoly : PolyElement
- polynomial `\check m_{\alpha} \in \mathbb Z[z]`, not necessarily
- irreducible
- p : Integer
- prime number, modulus of `\mathbb Z_p`
- Returns
- =======
- ftrunc : PolyElement
- polynomial in `\mathbb Z[x, z]`, reduced modulo
- `\check m_{\alpha}(z)` and `p`
- """
- ring = f.ring
- minpoly = minpoly.set_ring(ring)
- p_ = ring.ground_new(p)
- return f.trunc_ground(p).rem([minpoly, p_]).trunc_ground(p)
- def _euclidean_algorithm(f, g, minpoly, p):
- r"""
- Compute the monic GCD of two univariate polynomials in
- `\mathbb{Z}_p[z]/(\check m_{\alpha}(z))[x]` with the Euclidean
- Algorithm.
- In general, `\check m_{\alpha}(z)` is not irreducible, so it is possible
- that some leading coefficient is not invertible modulo
- `\check m_{\alpha}(z)`. In that case ``None`` is returned.
- Parameters
- ==========
- f, g : PolyElement
- polynomials in `\mathbb Z[x, z]`
- minpoly : PolyElement
- polynomial in `\mathbb Z[z]`, not necessarily irreducible
- p : Integer
- prime number, modulus of `\mathbb Z_p`
- Returns
- =======
- h : PolyElement
- GCD of `f` and `g` in `\mathbb Z[z, x]` or ``None``, coefficients
- are in `\left[ -\frac{p-1} 2, \frac{p-1} 2 \right]`
- """
- ring = f.ring
- f = _trunc(f, minpoly, p)
- g = _trunc(g, minpoly, p)
- while g:
- rem = f
- deg = g.degree(0) # degree in x
- lcinv, _, gcd = _gf_gcdex(ring.dmp_LC(g), minpoly, p)
- if not gcd == 1:
- return None
- while True:
- degrem = rem.degree(0) # degree in x
- if degrem < deg:
- break
- quo = (lcinv * ring.dmp_LC(rem)).set_ring(ring)
- rem = _trunc(rem - g.mul_monom((degrem - deg, 0))*quo, minpoly, p)
- f = g
- g = rem
- lcfinv = _gf_gcdex(ring.dmp_LC(f), minpoly, p)[0].set_ring(ring)
- return _trunc(f * lcfinv, minpoly, p)
- def _trial_division(f, h, minpoly, p=None):
- r"""
- Check if `h` divides `f` in
- `\mathbb K[t_1, \ldots, t_k][z]/(m_{\alpha}(z))`, where `\mathbb K` is
- either `\mathbb Q` or `\mathbb Z_p`.
- This algorithm is based on pseudo division and does not use any
- fractions. By default `\mathbb K` is `\mathbb Q`, if a prime number `p`
- is given, `\mathbb Z_p` is chosen instead.
- Parameters
- ==========
- f, h : PolyElement
- polynomials in `\mathbb Z[t_1, \ldots, t_k][x, z]`
- minpoly : PolyElement
- polynomial `m_{\alpha}(z)` in `\mathbb Z[t_1, \ldots, t_k][z]`
- p : Integer or None
- if `p` is given, `\mathbb K` is set to `\mathbb Z_p` instead of
- `\mathbb Q`, default is ``None``
- Returns
- =======
- rem : PolyElement
- remainder of `\frac f h`
- References
- ==========
- .. [1] [Hoeij02]_
- """
- ring = f.ring
- zxring = ring.clone(symbols=(ring.symbols[1], ring.symbols[0]))
- minpoly = minpoly.set_ring(ring)
- rem = f
- degrem = rem.degree()
- degh = h.degree()
- degm = minpoly.degree(1)
- lch = _LC(h).set_ring(ring)
- lcm = minpoly.LC
- while rem and degrem >= degh:
- # polynomial in Z[t_1, ..., t_k][z]
- lcrem = _LC(rem).set_ring(ring)
- rem = rem*lch - h.mul_monom((degrem - degh, 0))*lcrem
- if p:
- rem = rem.trunc_ground(p)
- degrem = rem.degree(1)
- while rem and degrem >= degm:
- # polynomial in Z[t_1, ..., t_k][x]
- lcrem = _LC(rem.set_ring(zxring)).set_ring(ring)
- rem = rem.mul_ground(lcm) - minpoly.mul_monom((0, degrem - degm))*lcrem
- if p:
- rem = rem.trunc_ground(p)
- degrem = rem.degree(1)
- degrem = rem.degree()
- return rem
- def _evaluate_ground(f, i, a):
- r"""
- Evaluate a polynomial `f` at `a` in the `i`-th variable of the ground
- domain.
- """
- ring = f.ring.clone(domain=f.ring.domain.ring.drop(i))
- fa = ring.zero
- for monom, coeff in f.iterterms():
- fa[monom] = coeff.evaluate(i, a)
- return fa
- def _func_field_modgcd_p(f, g, minpoly, p):
- r"""
- Compute the GCD of two polynomials `f` and `g` in
- `\mathbb Z_p(t_1, \ldots, t_k)[z]/(\check m_\alpha(z))[x]`.
- The algorithm reduces the problem step by step by evaluating the
- polynomials `f` and `g` at `t_k = a` for suitable `a \in \mathbb Z_p`
- and then calls itself recursively to compute the GCD in
- `\mathbb Z_p(t_1, \ldots, t_{k-1})[z]/(\check m_\alpha(z))[x]`. If these
- recursive calls are successful, the GCD over `k` variables is
- interpolated, otherwise the algorithm returns ``None``. After
- interpolation, Rational Function Reconstruction is used to obtain the
- correct coefficients. If this fails, a new evaluation point has to be
- chosen, otherwise the desired polynomial is obtained by clearing
- denominators. The result is verified with a fraction free trial
- division.
- Parameters
- ==========
- f, g : PolyElement
- polynomials in `\mathbb Z[t_1, \ldots, t_k][x, z]`
- minpoly : PolyElement
- polynomial in `\mathbb Z[t_1, \ldots, t_k][z]`, not necessarily
- irreducible
- p : Integer
- prime number, modulus of `\mathbb Z_p`
- Returns
- =======
- h : PolyElement
- primitive associate in `\mathbb Z[t_1, \ldots, t_k][x, z]` of the
- GCD of the polynomials `f` and `g` or ``None``, coefficients are
- in `\left[ -\frac{p-1} 2, \frac{p-1} 2 \right]`
- References
- ==========
- 1. [Hoeij04]_
- """
- ring = f.ring
- domain = ring.domain # Z[t_1, ..., t_k]
- if isinstance(domain, PolynomialRing):
- k = domain.ngens
- else:
- return _euclidean_algorithm(f, g, minpoly, p)
- if k == 1:
- qdomain = domain.ring.to_field()
- else:
- qdomain = domain.ring.drop_to_ground(k - 1)
- qdomain = qdomain.clone(domain=qdomain.domain.ring.to_field())
- qring = ring.clone(domain=qdomain) # = Z(t_k)[t_1, ..., t_{k-1}][x, z]
- n = 1
- d = 1
- # polynomial in Z_p[t_1, ..., t_k][z]
- gamma = ring.dmp_LC(f) * ring.dmp_LC(g)
- # polynomial in Z_p[t_1, ..., t_k]
- delta = minpoly.LC
- evalpoints = []
- heval = []
- LMlist = []
- points = list(range(p))
- while points:
- a = random.sample(points, 1)[0]
- points.remove(a)
- if k == 1:
- test = delta.evaluate(k-1, a) % p == 0
- else:
- test = delta.evaluate(k-1, a).trunc_ground(p) == 0
- if test:
- continue
- gammaa = _evaluate_ground(gamma, k-1, a)
- minpolya = _evaluate_ground(minpoly, k-1, a)
- if gammaa.rem([minpolya, gammaa.ring(p)]) == 0:
- continue
- fa = _evaluate_ground(f, k-1, a)
- ga = _evaluate_ground(g, k-1, a)
- # polynomial in Z_p[x, t_1, ..., t_{k-1}, z]/(minpoly)
- ha = _func_field_modgcd_p(fa, ga, minpolya, p)
- if ha is None:
- d += 1
- if d > n:
- return None
- continue
- if ha == 1:
- return ha
- LM = [ha.degree()] + [0]*(k-1)
- if k > 1:
- for monom, coeff in ha.iterterms():
- if monom[0] == LM[0] and coeff.LM > tuple(LM[1:]):
- LM[1:] = coeff.LM
- evalpoints_a = [a]
- heval_a = [ha]
- if k == 1:
- m = qring.domain.get_ring().one
- else:
- m = qring.domain.domain.get_ring().one
- t = m.ring.gens[0]
- for b, hb, LMhb in zip(evalpoints, heval, LMlist):
- if LMhb == LM:
- evalpoints_a.append(b)
- heval_a.append(hb)
- m *= (t - b)
- m = m.trunc_ground(p)
- evalpoints.append(a)
- heval.append(ha)
- LMlist.append(LM)
- n += 1
- # polynomial in Z_p[t_1, ..., t_k][x, z]
- h = _interpolate_multivariate(evalpoints_a, heval_a, ring, k-1, p, ground=True)
- # polynomial in Z_p(t_k)[t_1, ..., t_{k-1}][x, z]
- h = _rational_reconstruction_func_coeffs(h, p, m, qring, k-1)
- if h is None:
- continue
- if k == 1:
- dom = qring.domain.field
- den = dom.ring.one
- for coeff in h.itercoeffs():
- den = dom.ring.from_dense(gf_lcm(den.to_dense(), coeff.denom.to_dense(),
- p, dom.domain))
- else:
- dom = qring.domain.domain.field
- den = dom.ring.one
- for coeff in h.itercoeffs():
- for c in coeff.itercoeffs():
- den = dom.ring.from_dense(gf_lcm(den.to_dense(), c.denom.to_dense(),
- p, dom.domain))
- den = qring.domain_new(den.trunc_ground(p))
- h = ring(h.mul_ground(den).as_expr()).trunc_ground(p)
- if not _trial_division(f, h, minpoly, p) and not _trial_division(g, h, minpoly, p):
- return h
- return None
- def _integer_rational_reconstruction(c, m, domain):
- r"""
- Reconstruct a rational number `\frac a b` from
- .. math::
- c = \frac a b \; \mathrm{mod} \, m,
- where `c` and `m` are integers.
- The algorithm is based on the Euclidean Algorithm. In general, `m` is
- not a prime number, so it is possible that `b` is not invertible modulo
- `m`. In that case ``None`` is returned.
- Parameters
- ==========
- c : Integer
- `c = \frac a b \; \mathrm{mod} \, m`
- m : Integer
- modulus, not necessarily prime
- domain : IntegerRing
- `a, b, c` are elements of ``domain``
- Returns
- =======
- frac : Rational
- either `\frac a b` in `\mathbb Q` or ``None``
- References
- ==========
- 1. [Wang81]_
- """
- if c < 0:
- c += m
- r0, s0 = m, domain.zero
- r1, s1 = c, domain.one
- bound = sqrt(m / 2) # still correct if replaced by ZZ.sqrt(m // 2) ?
- while r1 >= bound:
- quo = r0 // r1
- r0, r1 = r1, r0 - quo*r1
- s0, s1 = s1, s0 - quo*s1
- if abs(s1) >= bound:
- return None
- if s1 < 0:
- a, b = -r1, -s1
- elif s1 > 0:
- a, b = r1, s1
- else:
- return None
- field = domain.get_field()
- return field(a) / field(b)
- def _rational_reconstruction_int_coeffs(hm, m, ring):
- r"""
- Reconstruct every rational coefficient `c_h` of a polynomial `h` in
- `\mathbb Q[t_1, \ldots, t_k][x, z]` from the corresponding integer
- coefficient `c_{h_m}` of a polynomial `h_m` in
- `\mathbb Z[t_1, \ldots, t_k][x, z]` such that
- .. math::
- c_{h_m} = c_h \; \mathrm{mod} \, m,
- where `m \in \mathbb Z`.
- The reconstruction is based on the Euclidean Algorithm. In general,
- `m` is not a prime number, so it is possible that this fails for some
- coefficient. In that case ``None`` is returned.
- Parameters
- ==========
- hm : PolyElement
- polynomial in `\mathbb Z[t_1, \ldots, t_k][x, z]`
- m : Integer
- modulus, not necessarily prime
- ring : PolyRing
- `\mathbb Q[t_1, \ldots, t_k][x, z]`, `h` will be an element of this
- ring
- Returns
- =======
- h : PolyElement
- reconstructed polynomial in `\mathbb Q[t_1, \ldots, t_k][x, z]` or
- ``None``
- See also
- ========
- _integer_rational_reconstruction
- """
- h = ring.zero
- if isinstance(ring.domain, PolynomialRing):
- reconstruction = _rational_reconstruction_int_coeffs
- domain = ring.domain.ring
- else:
- reconstruction = _integer_rational_reconstruction
- domain = hm.ring.domain
- for monom, coeff in hm.iterterms():
- coeffh = reconstruction(coeff, m, domain)
- if not coeffh:
- return None
- h[monom] = coeffh
- return h
- def _func_field_modgcd_m(f, g, minpoly):
- r"""
- Compute the GCD of two polynomials in
- `\mathbb Q(t_1, \ldots, t_k)[z]/(m_{\alpha}(z))[x]` using a modular
- algorithm.
- The algorithm computes the GCD of two polynomials `f` and `g` by
- calculating the GCD in
- `\mathbb Z_p(t_1, \ldots, t_k)[z] / (\check m_{\alpha}(z))[x]` for
- suitable primes `p` and the primitive associate `\check m_{\alpha}(z)`
- of `m_{\alpha}(z)`. Then the coefficients are reconstructed with the
- Chinese Remainder Theorem and Rational Reconstruction. To compute the
- GCD over `\mathbb Z_p(t_1, \ldots, t_k)[z] / (\check m_{\alpha})[x]`,
- the recursive subroutine ``_func_field_modgcd_p`` is used. To verify the
- result in `\mathbb Q(t_1, \ldots, t_k)[z] / (m_{\alpha}(z))[x]`, a
- fraction free trial division is used.
- Parameters
- ==========
- f, g : PolyElement
- polynomials in `\mathbb Z[t_1, \ldots, t_k][x, z]`
- minpoly : PolyElement
- irreducible polynomial in `\mathbb Z[t_1, \ldots, t_k][z]`
- Returns
- =======
- h : PolyElement
- the primitive associate in `\mathbb Z[t_1, \ldots, t_k][x, z]` of
- the GCD of `f` and `g`
- Examples
- ========
- >>> from sympy.polys.modulargcd import _func_field_modgcd_m
- >>> from sympy.polys import ring, ZZ
- >>> R, x, z = ring('x, z', ZZ)
- >>> minpoly = (z**2 - 2).drop(0)
- >>> f = x**2 + 2*x*z + 2
- >>> g = x + z
- >>> _func_field_modgcd_m(f, g, minpoly)
- x + z
- >>> D, t = ring('t', ZZ)
- >>> R, x, z = ring('x, z', D)
- >>> minpoly = (z**2-3).drop(0)
- >>> f = x**2 + (t + 1)*x*z + 3*t
- >>> g = x*z + 3*t
- >>> _func_field_modgcd_m(f, g, minpoly)
- x + t*z
- References
- ==========
- 1. [Hoeij04]_
- See also
- ========
- _func_field_modgcd_p
- """
- ring = f.ring
- domain = ring.domain
- if isinstance(domain, PolynomialRing):
- k = domain.ngens
- QQdomain = domain.ring.clone(domain=domain.domain.get_field())
- QQring = ring.clone(domain=QQdomain)
- else:
- k = 0
- QQring = ring.clone(domain=ring.domain.get_field())
- cf, f = f.primitive()
- cg, g = g.primitive()
- # polynomial in Z[t_1, ..., t_k][z]
- gamma = ring.dmp_LC(f) * ring.dmp_LC(g)
- # polynomial in Z[t_1, ..., t_k]
- delta = minpoly.LC
- p = 1
- primes = []
- hplist = []
- LMlist = []
- while True:
- p = nextprime(p)
- if gamma.trunc_ground(p) == 0:
- continue
- if k == 0:
- test = (delta % p == 0)
- else:
- test = (delta.trunc_ground(p) == 0)
- if test:
- continue
- fp = f.trunc_ground(p)
- gp = g.trunc_ground(p)
- minpolyp = minpoly.trunc_ground(p)
- hp = _func_field_modgcd_p(fp, gp, minpolyp, p)
- if hp is None:
- continue
- if hp == 1:
- return ring.one
- LM = [hp.degree()] + [0]*k
- if k > 0:
- for monom, coeff in hp.iterterms():
- if monom[0] == LM[0] and coeff.LM > tuple(LM[1:]):
- LM[1:] = coeff.LM
- hm = hp
- m = p
- for q, hq, LMhq in zip(primes, hplist, LMlist):
- if LMhq == LM:
- hm = _chinese_remainder_reconstruction_multivariate(hq, hm, q, m)
- m *= q
- primes.append(p)
- hplist.append(hp)
- LMlist.append(LM)
- hm = _rational_reconstruction_int_coeffs(hm, m, QQring)
- if hm is None:
- continue
- if k == 0:
- h = hm.clear_denoms()[1]
- else:
- den = domain.domain.one
- for coeff in hm.itercoeffs():
- den = domain.domain.lcm(den, coeff.clear_denoms()[0])
- h = hm.mul_ground(den)
- # convert back to Z[t_1, ..., t_k][x, z] from Q[t_1, ..., t_k][x, z]
- h = h.set_ring(ring)
- h = h.primitive()[1]
- if not (_trial_division(f.mul_ground(cf), h, minpoly) or
- _trial_division(g.mul_ground(cg), h, minpoly)):
- return h
- def _to_ZZ_poly(f, ring):
- r"""
- Compute an associate of a polynomial
- `f \in \mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]` in
- `\mathbb Z[x_1, \ldots, x_{n-1}][z] / (\check m_{\alpha}(z))[x_0]`,
- where `\check m_{\alpha}(z) \in \mathbb Z[z]` is the primitive associate
- of the minimal polynomial `m_{\alpha}(z)` of `\alpha` over
- `\mathbb Q`.
- Parameters
- ==========
- f : PolyElement
- polynomial in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
- ring : PolyRing
- `\mathbb Z[x_1, \ldots, x_{n-1}][x_0, z]`
- Returns
- =======
- f_ : PolyElement
- associate of `f` in
- `\mathbb Z[x_1, \ldots, x_{n-1}][x_0, z]`
- """
- f_ = ring.zero
- if isinstance(ring.domain, PolynomialRing):
- domain = ring.domain.domain
- else:
- domain = ring.domain
- den = domain.one
- for coeff in f.itercoeffs():
- for c in coeff.rep:
- if c:
- den = domain.lcm(den, c.denominator)
- for monom, coeff in f.iterterms():
- coeff = coeff.rep
- m = ring.domain.one
- if isinstance(ring.domain, PolynomialRing):
- m = m.mul_monom(monom[1:])
- n = len(coeff)
- for i in range(n):
- if coeff[i]:
- c = domain(coeff[i] * den) * m
- if (monom[0], n-i-1) not in f_:
- f_[(monom[0], n-i-1)] = c
- else:
- f_[(monom[0], n-i-1)] += c
- return f_
- def _to_ANP_poly(f, ring):
- r"""
- Convert a polynomial
- `f \in \mathbb Z[x_1, \ldots, x_{n-1}][z]/(\check m_{\alpha}(z))[x_0]`
- to a polynomial in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`,
- where `\check m_{\alpha}(z) \in \mathbb Z[z]` is the primitive associate
- of the minimal polynomial `m_{\alpha}(z)` of `\alpha` over
- `\mathbb Q`.
- Parameters
- ==========
- f : PolyElement
- polynomial in `\mathbb Z[x_1, \ldots, x_{n-1}][x_0, z]`
- ring : PolyRing
- `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
- Returns
- =======
- f_ : PolyElement
- polynomial in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
- """
- domain = ring.domain
- f_ = ring.zero
- if isinstance(f.ring.domain, PolynomialRing):
- for monom, coeff in f.iterterms():
- for mon, coef in coeff.iterterms():
- m = (monom[0],) + mon
- c = domain([domain.domain(coef)] + [0]*monom[1])
- if m not in f_:
- f_[m] = c
- else:
- f_[m] += c
- else:
- for monom, coeff in f.iterterms():
- m = (monom[0],)
- c = domain([domain.domain(coeff)] + [0]*monom[1])
- if m not in f_:
- f_[m] = c
- else:
- f_[m] += c
- return f_
- def _minpoly_from_dense(minpoly, ring):
- r"""
- Change representation of the minimal polynomial from ``DMP`` to
- ``PolyElement`` for a given ring.
- """
- minpoly_ = ring.zero
- for monom, coeff in minpoly.terms():
- minpoly_[monom] = ring.domain(coeff)
- return minpoly_
- def _primitive_in_x0(f):
- r"""
- Compute the content in `x_0` and the primitive part of a polynomial `f`
- in
- `\mathbb Q(\alpha)[x_0, x_1, \ldots, x_{n-1}] \cong \mathbb Q(\alpha)[x_1, \ldots, x_{n-1}][x_0]`.
- """
- fring = f.ring
- ring = fring.drop_to_ground(*range(1, fring.ngens))
- dom = ring.domain.ring
- f_ = ring(f.as_expr())
- cont = dom.zero
- for coeff in f_.itercoeffs():
- cont = func_field_modgcd(cont, coeff)[0]
- if cont == dom.one:
- return cont, f
- return cont, f.quo(cont.set_ring(fring))
- # TODO: add support for algebraic function fields
- def func_field_modgcd(f, g):
- r"""
- Compute the GCD of two polynomials `f` and `g` in
- `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]` using a modular algorithm.
- The algorithm first computes the primitive associate
- `\check m_{\alpha}(z)` of the minimal polynomial `m_{\alpha}` in
- `\mathbb{Z}[z]` and the primitive associates of `f` and `g` in
- `\mathbb{Z}[x_1, \ldots, x_{n-1}][z]/(\check m_{\alpha})[x_0]`. Then it
- computes the GCD in
- `\mathbb Q(x_1, \ldots, x_{n-1})[z]/(m_{\alpha}(z))[x_0]`.
- This is done by calculating the GCD in
- `\mathbb{Z}_p(x_1, \ldots, x_{n-1})[z]/(\check m_{\alpha}(z))[x_0]` for
- suitable primes `p` and then reconstructing the coefficients with the
- Chinese Remainder Theorem and Rational Reconstuction. The GCD over
- `\mathbb{Z}_p(x_1, \ldots, x_{n-1})[z]/(\check m_{\alpha}(z))[x_0]` is
- computed with a recursive subroutine, which evaluates the polynomials at
- `x_{n-1} = a` for suitable evaluation points `a \in \mathbb Z_p` and
- then calls itself recursively until the ground domain does no longer
- contain any parameters. For
- `\mathbb{Z}_p[z]/(\check m_{\alpha}(z))[x_0]` the Euclidean Algorithm is
- used. The results of those recursive calls are then interpolated and
- Rational Function Reconstruction is used to obtain the correct
- coefficients. The results, both in
- `\mathbb Q(x_1, \ldots, x_{n-1})[z]/(m_{\alpha}(z))[x_0]` and
- `\mathbb{Z}_p(x_1, \ldots, x_{n-1})[z]/(\check m_{\alpha}(z))[x_0]`, are
- verified by a fraction free trial division.
- Apart from the above GCD computation some GCDs in
- `\mathbb Q(\alpha)[x_1, \ldots, x_{n-1}]` have to be calculated,
- because treating the polynomials as univariate ones can result in
- a spurious content of the GCD. For this ``func_field_modgcd`` is
- called recursively.
- Parameters
- ==========
- f, g : PolyElement
- polynomials in `\mathbb Q(\alpha)[x_0, \ldots, x_{n-1}]`
- Returns
- =======
- h : PolyElement
- monic GCD of the polynomials `f` and `g`
- cff : PolyElement
- cofactor of `f`, i.e. `\frac f h`
- cfg : PolyElement
- cofactor of `g`, i.e. `\frac g h`
- Examples
- ========
- >>> from sympy.polys.modulargcd import func_field_modgcd
- >>> from sympy.polys import AlgebraicField, QQ, ring
- >>> from sympy import sqrt
- >>> A = AlgebraicField(QQ, sqrt(2))
- >>> R, x = ring('x', A)
- >>> f = x**2 - 2
- >>> g = x + sqrt(2)
- >>> h, cff, cfg = func_field_modgcd(f, g)
- >>> h == x + sqrt(2)
- True
- >>> cff * h == f
- True
- >>> cfg * h == g
- True
- >>> R, x, y = ring('x, y', A)
- >>> f = x**2 + 2*sqrt(2)*x*y + 2*y**2
- >>> g = x + sqrt(2)*y
- >>> h, cff, cfg = func_field_modgcd(f, g)
- >>> h == x + sqrt(2)*y
- True
- >>> cff * h == f
- True
- >>> cfg * h == g
- True
- >>> f = x + sqrt(2)*y
- >>> g = x + y
- >>> h, cff, cfg = func_field_modgcd(f, g)
- >>> h == R.one
- True
- >>> cff * h == f
- True
- >>> cfg * h == g
- True
- References
- ==========
- 1. [Hoeij04]_
- """
- ring = f.ring
- domain = ring.domain
- n = ring.ngens
- assert ring == g.ring and domain.is_Algebraic
- result = _trivial_gcd(f, g)
- if result is not None:
- return result
- z = Dummy('z')
- ZZring = ring.clone(symbols=ring.symbols + (z,), domain=domain.domain.get_ring())
- if n == 1:
- f_ = _to_ZZ_poly(f, ZZring)
- g_ = _to_ZZ_poly(g, ZZring)
- minpoly = ZZring.drop(0).from_dense(domain.mod.rep)
- h = _func_field_modgcd_m(f_, g_, minpoly)
- h = _to_ANP_poly(h, ring)
- else:
- # contx0f in Q(a)[x_1, ..., x_{n-1}], f in Q(a)[x_0, ..., x_{n-1}]
- contx0f, f = _primitive_in_x0(f)
- contx0g, g = _primitive_in_x0(g)
- contx0h = func_field_modgcd(contx0f, contx0g)[0]
- ZZring_ = ZZring.drop_to_ground(*range(1, n))
- f_ = _to_ZZ_poly(f, ZZring_)
- g_ = _to_ZZ_poly(g, ZZring_)
- minpoly = _minpoly_from_dense(domain.mod, ZZring_.drop(0))
- h = _func_field_modgcd_m(f_, g_, minpoly)
- h = _to_ANP_poly(h, ring)
- contx0h_, h = _primitive_in_x0(h)
- h *= contx0h.set_ring(ring)
- f *= contx0f.set_ring(ring)
- g *= contx0g.set_ring(ring)
- h = h.quo_ground(h.LC)
- return h, f.quo(h), g.quo(h)
|