123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025 |
- """Power series evaluation and manipulation using sparse Polynomials
- Implementing a new function
- ---------------------------
- There are a few things to be kept in mind when adding a new function here::
- - The implementation should work on all possible input domains/rings.
- Special cases include the ``EX`` ring and a constant term in the series
- to be expanded. There can be two types of constant terms in the series:
- + A constant value or symbol.
- + A term of a multivariate series not involving the generator, with
- respect to which the series is to expanded.
- Strictly speaking, a generator of a ring should not be considered a
- constant. However, for series expansion both the cases need similar
- treatment (as the user doesn't care about inner details), i.e, use an
- addition formula to separate the constant part and the variable part (see
- rs_sin for reference).
- - All the algorithms used here are primarily designed to work for Taylor
- series (number of iterations in the algo equals the required order).
- Hence, it becomes tricky to get the series of the right order if a
- Puiseux series is input. Use rs_puiseux? in your function if your
- algorithm is not designed to handle fractional powers.
- Extending rs_series
- -------------------
- To make a function work with rs_series you need to do two things::
- - Many sure it works with a constant term (as explained above).
- - If the series contains constant terms, you might need to extend its ring.
- You do so by adding the new terms to the rings as generators.
- ``PolyRing.compose`` and ``PolyRing.add_gens`` are two functions that do
- so and need to be called every time you expand a series containing a
- constant term.
- Look at rs_sin and rs_series for further reference.
- """
- from sympy.polys.domains import QQ, EX
- from sympy.polys.rings import PolyElement, ring, sring
- from sympy.polys.polyerrors import DomainError
- from sympy.polys.monomials import (monomial_min, monomial_mul, monomial_div,
- monomial_ldiv)
- from mpmath.libmp.libintmath import ifac
- from sympy.core import PoleError, Function, Expr
- from sympy.core.numbers import Rational, igcd
- from sympy.functions import sin, cos, tan, atan, exp, atanh, tanh, log, ceiling
- from sympy.utilities.misc import as_int
- from mpmath.libmp.libintmath import giant_steps
- import math
- def _invert_monoms(p1):
- """
- Compute ``x**n * p1(1/x)`` for a univariate polynomial ``p1`` in ``x``.
- Examples
- ========
- >>> from sympy.polys.domains import ZZ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import _invert_monoms
- >>> R, x = ring('x', ZZ)
- >>> p = x**2 + 2*x + 3
- >>> _invert_monoms(p)
- 3*x**2 + 2*x + 1
- See Also
- ========
- sympy.polys.densebasic.dup_reverse
- """
- terms = list(p1.items())
- terms.sort()
- deg = p1.degree()
- R = p1.ring
- p = R.zero
- cv = p1.listcoeffs()
- mv = p1.listmonoms()
- for i in range(len(mv)):
- p[(deg - mv[i][0],)] = cv[i]
- return p
- def _giant_steps(target):
- """Return a list of precision steps for the Newton's method"""
- res = giant_steps(2, target)
- if res[0] != 2:
- res = [2] + res
- return res
- def rs_trunc(p1, x, prec):
- """
- Truncate the series in the ``x`` variable with precision ``prec``,
- that is, modulo ``O(x**prec)``
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_trunc
- >>> R, x = ring('x', QQ)
- >>> p = x**10 + x**5 + x + 1
- >>> rs_trunc(p, x, 12)
- x**10 + x**5 + x + 1
- >>> rs_trunc(p, x, 10)
- x**5 + x + 1
- """
- R = p1.ring
- p = R.zero
- i = R.gens.index(x)
- for exp1 in p1:
- if exp1[i] >= prec:
- continue
- p[exp1] = p1[exp1]
- return p
- def rs_is_puiseux(p, x):
- """
- Test if ``p`` is Puiseux series in ``x``.
- Raise an exception if it has a negative power in ``x``.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_is_puiseux
- >>> R, x = ring('x', QQ)
- >>> p = x**QQ(2,5) + x**QQ(2,3) + x
- >>> rs_is_puiseux(p, x)
- True
- """
- index = p.ring.gens.index(x)
- for k in p:
- if k[index] != int(k[index]):
- return True
- if k[index] < 0:
- raise ValueError('The series is not regular in %s' % x)
- return False
- def rs_puiseux(f, p, x, prec):
- """
- Return the puiseux series for `f(p, x, prec)`.
- To be used when function ``f`` is implemented only for regular series.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_puiseux, rs_exp
- >>> R, x = ring('x', QQ)
- >>> p = x**QQ(2,5) + x**QQ(2,3) + x
- >>> rs_puiseux(rs_exp,p, x, 1)
- 1/2*x**(4/5) + x**(2/3) + x**(2/5) + 1
- """
- index = p.ring.gens.index(x)
- n = 1
- for k in p:
- power = k[index]
- if isinstance(power, Rational):
- num, den = power.as_numer_denom()
- n = int(n*den // igcd(n, den))
- elif power != int(power):
- den = power.denominator
- n = int(n*den // igcd(n, den))
- if n != 1:
- p1 = pow_xin(p, index, n)
- r = f(p1, x, prec*n)
- n1 = QQ(1, n)
- if isinstance(r, tuple):
- r = tuple([pow_xin(rx, index, n1) for rx in r])
- else:
- r = pow_xin(r, index, n1)
- else:
- r = f(p, x, prec)
- return r
- def rs_puiseux2(f, p, q, x, prec):
- """
- Return the puiseux series for `f(p, q, x, prec)`.
- To be used when function ``f`` is implemented only for regular series.
- """
- index = p.ring.gens.index(x)
- n = 1
- for k in p:
- power = k[index]
- if isinstance(power, Rational):
- num, den = power.as_numer_denom()
- n = n*den // igcd(n, den)
- elif power != int(power):
- den = power.denominator
- n = n*den // igcd(n, den)
- if n != 1:
- p1 = pow_xin(p, index, n)
- r = f(p1, q, x, prec*n)
- n1 = QQ(1, n)
- r = pow_xin(r, index, n1)
- else:
- r = f(p, q, x, prec)
- return r
- def rs_mul(p1, p2, x, prec):
- """
- Return the product of the given two series, modulo ``O(x**prec)``.
- ``x`` is the series variable or its position in the generators.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_mul
- >>> R, x = ring('x', QQ)
- >>> p1 = x**2 + 2*x + 1
- >>> p2 = x + 1
- >>> rs_mul(p1, p2, x, 3)
- 3*x**2 + 3*x + 1
- """
- R = p1.ring
- p = R.zero
- if R.__class__ != p2.ring.__class__ or R != p2.ring:
- raise ValueError('p1 and p2 must have the same ring')
- iv = R.gens.index(x)
- if not isinstance(p2, PolyElement):
- raise ValueError('p1 and p2 must have the same ring')
- if R == p2.ring:
- get = p.get
- items2 = list(p2.items())
- items2.sort(key=lambda e: e[0][iv])
- if R.ngens == 1:
- for exp1, v1 in p1.items():
- for exp2, v2 in items2:
- exp = exp1[0] + exp2[0]
- if exp < prec:
- exp = (exp, )
- p[exp] = get(exp, 0) + v1*v2
- else:
- break
- else:
- monomial_mul = R.monomial_mul
- for exp1, v1 in p1.items():
- for exp2, v2 in items2:
- if exp1[iv] + exp2[iv] < prec:
- exp = monomial_mul(exp1, exp2)
- p[exp] = get(exp, 0) + v1*v2
- else:
- break
- p.strip_zero()
- return p
- def rs_square(p1, x, prec):
- """
- Square the series modulo ``O(x**prec)``
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_square
- >>> R, x = ring('x', QQ)
- >>> p = x**2 + 2*x + 1
- >>> rs_square(p, x, 3)
- 6*x**2 + 4*x + 1
- """
- R = p1.ring
- p = R.zero
- iv = R.gens.index(x)
- get = p.get
- items = list(p1.items())
- items.sort(key=lambda e: e[0][iv])
- monomial_mul = R.monomial_mul
- for i in range(len(items)):
- exp1, v1 = items[i]
- for j in range(i):
- exp2, v2 = items[j]
- if exp1[iv] + exp2[iv] < prec:
- exp = monomial_mul(exp1, exp2)
- p[exp] = get(exp, 0) + v1*v2
- else:
- break
- p = p.imul_num(2)
- get = p.get
- for expv, v in p1.items():
- if 2*expv[iv] < prec:
- e2 = monomial_mul(expv, expv)
- p[e2] = get(e2, 0) + v**2
- p.strip_zero()
- return p
- def rs_pow(p1, n, x, prec):
- """
- Return ``p1**n`` modulo ``O(x**prec)``
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_pow
- >>> R, x = ring('x', QQ)
- >>> p = x + 1
- >>> rs_pow(p, 4, x, 3)
- 6*x**2 + 4*x + 1
- """
- R = p1.ring
- if isinstance(n, Rational):
- np = int(n.p)
- nq = int(n.q)
- if nq != 1:
- res = rs_nth_root(p1, nq, x, prec)
- if np != 1:
- res = rs_pow(res, np, x, prec)
- else:
- res = rs_pow(p1, np, x, prec)
- return res
- n = as_int(n)
- if n == 0:
- if p1:
- return R(1)
- else:
- raise ValueError('0**0 is undefined')
- if n < 0:
- p1 = rs_pow(p1, -n, x, prec)
- return rs_series_inversion(p1, x, prec)
- if n == 1:
- return rs_trunc(p1, x, prec)
- if n == 2:
- return rs_square(p1, x, prec)
- if n == 3:
- p2 = rs_square(p1, x, prec)
- return rs_mul(p1, p2, x, prec)
- p = R(1)
- while 1:
- if n & 1:
- p = rs_mul(p1, p, x, prec)
- n -= 1
- if not n:
- break
- p1 = rs_square(p1, x, prec)
- n = n // 2
- return p
- def rs_subs(p, rules, x, prec):
- """
- Substitution with truncation according to the mapping in ``rules``.
- Return a series with precision ``prec`` in the generator ``x``
- Note that substitutions are not done one after the other
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_subs
- >>> R, x, y = ring('x, y', QQ)
- >>> p = x**2 + y**2
- >>> rs_subs(p, {x: x+ y, y: x+ 2*y}, x, 3)
- 2*x**2 + 6*x*y + 5*y**2
- >>> (x + y)**2 + (x + 2*y)**2
- 2*x**2 + 6*x*y + 5*y**2
- which differs from
- >>> rs_subs(rs_subs(p, {x: x+ y}, x, 3), {y: x+ 2*y}, x, 3)
- 5*x**2 + 12*x*y + 8*y**2
- Parameters
- ----------
- p : :class:`~.PolyElement` Input series.
- rules : ``dict`` with substitution mappings.
- x : :class:`~.PolyElement` in which the series truncation is to be done.
- prec : :class:`~.Integer` order of the series after truncation.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_subs
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_subs(x**2+y**2, {y: (x+y)**2}, x, 3)
- 6*x**2*y**2 + x**2 + 4*x*y**3 + y**4
- """
- R = p.ring
- ngens = R.ngens
- d = R(0)
- for i in range(ngens):
- d[(i, 1)] = R.gens[i]
- for var in rules:
- d[(R.index(var), 1)] = rules[var]
- p1 = R(0)
- p_keys = sorted(p.keys())
- for expv in p_keys:
- p2 = R(1)
- for i in range(ngens):
- power = expv[i]
- if power == 0:
- continue
- if (i, power) not in d:
- q, r = divmod(power, 2)
- if r == 0 and (i, q) in d:
- d[(i, power)] = rs_square(d[(i, q)], x, prec)
- elif (i, power - 1) in d:
- d[(i, power)] = rs_mul(d[(i, power - 1)], d[(i, 1)],
- x, prec)
- else:
- d[(i, power)] = rs_pow(d[(i, 1)], power, x, prec)
- p2 = rs_mul(p2, d[(i, power)], x, prec)
- p1 += p2*p[expv]
- return p1
- def _has_constant_term(p, x):
- """
- Check if ``p`` has a constant term in ``x``
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import _has_constant_term
- >>> R, x = ring('x', QQ)
- >>> p = x**2 + x + 1
- >>> _has_constant_term(p, x)
- True
- """
- R = p.ring
- iv = R.gens.index(x)
- zm = R.zero_monom
- a = [0]*R.ngens
- a[iv] = 1
- miv = tuple(a)
- for expv in p:
- if monomial_min(expv, miv) == zm:
- return True
- return False
- def _get_constant_term(p, x):
- """Return constant term in p with respect to x
- Note that it is not simply `p[R.zero_monom]` as there might be multiple
- generators in the ring R. We want the `x`-free term which can contain other
- generators.
- """
- R = p.ring
- i = R.gens.index(x)
- zm = R.zero_monom
- a = [0]*R.ngens
- a[i] = 1
- miv = tuple(a)
- c = 0
- for expv in p:
- if monomial_min(expv, miv) == zm:
- c += R({expv: p[expv]})
- return c
- def _check_series_var(p, x, name):
- index = p.ring.gens.index(x)
- m = min(p, key=lambda k: k[index])[index]
- if m < 0:
- raise PoleError("Asymptotic expansion of %s around [oo] not "
- "implemented." % name)
- return index, m
- def _series_inversion1(p, x, prec):
- """
- Univariate series inversion ``1/p`` modulo ``O(x**prec)``.
- The Newton method is used.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import _series_inversion1
- >>> R, x = ring('x', QQ)
- >>> p = x + 1
- >>> _series_inversion1(p, x, 4)
- -x**3 + x**2 - x + 1
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(_series_inversion1, p, x, prec)
- R = p.ring
- zm = R.zero_monom
- c = p[zm]
- # giant_steps does not seem to work with PythonRational numbers with 1 as
- # denominator. This makes sure such a number is converted to integer.
- if prec == int(prec):
- prec = int(prec)
- if zm not in p:
- raise ValueError("No constant term in series")
- if _has_constant_term(p - c, x):
- raise ValueError("p cannot contain a constant term depending on "
- "parameters")
- one = R(1)
- if R.domain is EX:
- one = 1
- if c != one:
- # TODO add check that it is a unit
- p1 = R(1)/c
- else:
- p1 = R(1)
- for precx in _giant_steps(prec):
- t = 1 - rs_mul(p1, p, x, precx)
- p1 = p1 + rs_mul(p1, t, x, precx)
- return p1
- def rs_series_inversion(p, x, prec):
- """
- Multivariate series inversion ``1/p`` modulo ``O(x**prec)``.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_series_inversion
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_series_inversion(1 + x*y**2, x, 4)
- -x**3*y**6 + x**2*y**4 - x*y**2 + 1
- >>> rs_series_inversion(1 + x*y**2, y, 4)
- -x*y**2 + 1
- >>> rs_series_inversion(x + x**2, x, 4)
- x**3 - x**2 + x - 1 + x**(-1)
- """
- R = p.ring
- if p == R.zero:
- raise ZeroDivisionError
- zm = R.zero_monom
- index = R.gens.index(x)
- m = min(p, key=lambda k: k[index])[index]
- if m:
- p = mul_xin(p, index, -m)
- prec = prec + m
- if zm not in p:
- raise NotImplementedError("No constant term in series")
- if _has_constant_term(p - p[zm], x):
- raise NotImplementedError("p - p[0] must not have a constant term in "
- "the series variables")
- r = _series_inversion1(p, x, prec)
- if m != 0:
- r = mul_xin(r, index, -m)
- return r
- def _coefficient_t(p, t):
- r"""Coefficient of `x_i**j` in p, where ``t`` = (i, j)"""
- i, j = t
- R = p.ring
- expv1 = [0]*R.ngens
- expv1[i] = j
- expv1 = tuple(expv1)
- p1 = R(0)
- for expv in p:
- if expv[i] == j:
- p1[monomial_div(expv, expv1)] = p[expv]
- return p1
- def rs_series_reversion(p, x, n, y):
- r"""
- Reversion of a series.
- ``p`` is a series with ``O(x**n)`` of the form $p = ax + f(x)$
- where $a$ is a number different from 0.
- $f(x) = \sum_{k=2}^{n-1} a_kx_k$
- Parameters
- ==========
- a_k : Can depend polynomially on other variables, not indicated.
- x : Variable with name x.
- y : Variable with name y.
- Returns
- =======
- Solve $p = y$, that is, given $ax + f(x) - y = 0$,
- find the solution $x = r(y)$ up to $O(y^n)$.
- Algorithm
- =========
- If $r_i$ is the solution at order $i$, then:
- $ar_i + f(r_i) - y = O\left(y^{i + 1}\right)$
- and if $r_{i + 1}$ is the solution at order $i + 1$, then:
- $ar_{i + 1} + f(r_{i + 1}) - y = O\left(y^{i + 2}\right)$
- We have, $r_{i + 1} = r_i + e$, such that,
- $ae + f(r_i) = O\left(y^{i + 2}\right)$
- or $e = -f(r_i)/a$
- So we use the recursion relation:
- $r_{i + 1} = r_i - f(r_i)/a$
- with the boundary condition: $r_1 = y$
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_series_reversion, rs_trunc
- >>> R, x, y, a, b = ring('x, y, a, b', QQ)
- >>> p = x - x**2 - 2*b*x**2 + 2*a*b*x**2
- >>> p1 = rs_series_reversion(p, x, 3, y); p1
- -2*y**2*a*b + 2*y**2*b + y**2 + y
- >>> rs_trunc(p.compose(x, p1), y, 3)
- y
- """
- if rs_is_puiseux(p, x):
- raise NotImplementedError
- R = p.ring
- nx = R.gens.index(x)
- y = R(y)
- ny = R.gens.index(y)
- if _has_constant_term(p, x):
- raise ValueError("p must not contain a constant term in the series "
- "variable")
- a = _coefficient_t(p, (nx, 1))
- zm = R.zero_monom
- assert zm in a and len(a) == 1
- a = a[zm]
- r = y/a
- for i in range(2, n):
- sp = rs_subs(p, {x: r}, y, i + 1)
- sp = _coefficient_t(sp, (ny, i))*y**i
- r -= sp/a
- return r
- def rs_series_from_list(p, c, x, prec, concur=1):
- """
- Return a series `sum c[n]*p**n` modulo `O(x**prec)`.
- It reduces the number of multiplications by summing concurrently.
- `ax = [1, p, p**2, .., p**(J - 1)]`
- `s = sum(c[i]*ax[i]` for i in `range(r, (r + 1)*J))*p**((K - 1)*J)`
- with `K >= (n + 1)/J`
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_series_from_list, rs_trunc
- >>> R, x = ring('x', QQ)
- >>> p = x**2 + x + 1
- >>> c = [1, 2, 3]
- >>> rs_series_from_list(p, c, x, 4)
- 6*x**3 + 11*x**2 + 8*x + 6
- >>> rs_trunc(1 + 2*p + 3*p**2, x, 4)
- 6*x**3 + 11*x**2 + 8*x + 6
- >>> pc = R.from_list(list(reversed(c)))
- >>> rs_trunc(pc.compose(x, p), x, 4)
- 6*x**3 + 11*x**2 + 8*x + 6
- """
- # TODO: Add this when it is documented in Sphinx
- """
- See Also
- ========
- sympy.polys.rings.PolyRing.compose
- """
- R = p.ring
- n = len(c)
- if not concur:
- q = R(1)
- s = c[0]*q
- for i in range(1, n):
- q = rs_mul(q, p, x, prec)
- s += c[i]*q
- return s
- J = int(math.sqrt(n) + 1)
- K, r = divmod(n, J)
- if r:
- K += 1
- ax = [R(1)]
- q = R(1)
- if len(p) < 20:
- for i in range(1, J):
- q = rs_mul(q, p, x, prec)
- ax.append(q)
- else:
- for i in range(1, J):
- if i % 2 == 0:
- q = rs_square(ax[i//2], x, prec)
- else:
- q = rs_mul(q, p, x, prec)
- ax.append(q)
- # optimize using rs_square
- pj = rs_mul(ax[-1], p, x, prec)
- b = R(1)
- s = R(0)
- for k in range(K - 1):
- r = J*k
- s1 = c[r]
- for j in range(1, J):
- s1 += c[r + j]*ax[j]
- s1 = rs_mul(s1, b, x, prec)
- s += s1
- b = rs_mul(b, pj, x, prec)
- if not b:
- break
- k = K - 1
- r = J*k
- if r < n:
- s1 = c[r]*R(1)
- for j in range(1, J):
- if r + j >= n:
- break
- s1 += c[r + j]*ax[j]
- s1 = rs_mul(s1, b, x, prec)
- s += s1
- return s
- def rs_diff(p, x):
- """
- Return partial derivative of ``p`` with respect to ``x``.
- Parameters
- ==========
- x : :class:`~.PolyElement` with respect to which ``p`` is differentiated.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_diff
- >>> R, x, y = ring('x, y', QQ)
- >>> p = x + x**2*y**3
- >>> rs_diff(p, x)
- 2*x*y**3 + 1
- """
- R = p.ring
- n = R.gens.index(x)
- p1 = R.zero
- mn = [0]*R.ngens
- mn[n] = 1
- mn = tuple(mn)
- for expv in p:
- if expv[n]:
- e = monomial_ldiv(expv, mn)
- p1[e] = R.domain_new(p[expv]*expv[n])
- return p1
- def rs_integrate(p, x):
- """
- Integrate ``p`` with respect to ``x``.
- Parameters
- ==========
- x : :class:`~.PolyElement` with respect to which ``p`` is integrated.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_integrate
- >>> R, x, y = ring('x, y', QQ)
- >>> p = x + x**2*y**3
- >>> rs_integrate(p, x)
- 1/3*x**3*y**3 + 1/2*x**2
- """
- R = p.ring
- p1 = R.zero
- n = R.gens.index(x)
- mn = [0]*R.ngens
- mn[n] = 1
- mn = tuple(mn)
- for expv in p:
- e = monomial_mul(expv, mn)
- p1[e] = R.domain_new(p[expv]/(expv[n] + 1))
- return p1
- def rs_fun(p, f, *args):
- r"""
- Function of a multivariate series computed by substitution.
- The case with f method name is used to compute `rs\_tan` and `rs\_nth\_root`
- of a multivariate series:
- `rs\_fun(p, tan, iv, prec)`
- tan series is first computed for a dummy variable _x,
- i.e, `rs\_tan(\_x, iv, prec)`. Then we substitute _x with p to get the
- desired series
- Parameters
- ==========
- p : :class:`~.PolyElement` The multivariate series to be expanded.
- f : `ring\_series` function to be applied on `p`.
- args[-2] : :class:`~.PolyElement` with respect to which, the series is to be expanded.
- args[-1] : Required order of the expanded series.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_fun, _tan1
- >>> R, x, y = ring('x, y', QQ)
- >>> p = x + x*y + x**2*y + x**3*y**2
- >>> rs_fun(p, _tan1, x, 4)
- 1/3*x**3*y**3 + 2*x**3*y**2 + x**3*y + 1/3*x**3 + x**2*y + x*y + x
- """
- _R = p.ring
- R1, _x = ring('_x', _R.domain)
- h = int(args[-1])
- args1 = args[:-2] + (_x, h)
- zm = _R.zero_monom
- # separate the constant term of the series
- # compute the univariate series f(_x, .., 'x', sum(nv))
- if zm in p:
- x1 = _x + p[zm]
- p1 = p - p[zm]
- else:
- x1 = _x
- p1 = p
- if isinstance(f, str):
- q = getattr(x1, f)(*args1)
- else:
- q = f(x1, *args1)
- a = sorted(q.items())
- c = [0]*h
- for x in a:
- c[x[0][0]] = x[1]
- p1 = rs_series_from_list(p1, c, args[-2], args[-1])
- return p1
- def mul_xin(p, i, n):
- r"""
- Return `p*x_i**n`.
- `x\_i` is the ith variable in ``p``.
- """
- R = p.ring
- q = R(0)
- for k, v in p.items():
- k1 = list(k)
- k1[i] += n
- q[tuple(k1)] = v
- return q
- def pow_xin(p, i, n):
- """
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import pow_xin
- >>> R, x, y = ring('x, y', QQ)
- >>> p = x**QQ(2,5) + x + x**QQ(2,3)
- >>> index = p.ring.gens.index(x)
- >>> pow_xin(p, index, 15)
- x**15 + x**10 + x**6
- """
- R = p.ring
- q = R(0)
- for k, v in p.items():
- k1 = list(k)
- k1[i] *= n
- q[tuple(k1)] = v
- return q
- def _nth_root1(p, n, x, prec):
- """
- Univariate series expansion of the nth root of ``p``.
- The Newton method is used.
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux2(_nth_root1, p, n, x, prec)
- R = p.ring
- zm = R.zero_monom
- if zm not in p:
- raise NotImplementedError('No constant term in series')
- n = as_int(n)
- assert p[zm] == 1
- p1 = R(1)
- if p == 1:
- return p
- if n == 0:
- return R(1)
- if n == 1:
- return p
- if n < 0:
- n = -n
- sign = 1
- else:
- sign = 0
- for precx in _giant_steps(prec):
- tmp = rs_pow(p1, n + 1, x, precx)
- tmp = rs_mul(tmp, p, x, precx)
- p1 += p1/n - tmp/n
- if sign:
- return p1
- else:
- return _series_inversion1(p1, x, prec)
- def rs_nth_root(p, n, x, prec):
- """
- Multivariate series expansion of the nth root of ``p``.
- Parameters
- ==========
- p : Expr
- The polynomial to computer the root of.
- n : integer
- The order of the root to be computed.
- x : :class:`~.PolyElement`
- prec : integer
- Order of the expanded series.
- Notes
- =====
- The result of this function is dependent on the ring over which the
- polynomial has been defined. If the answer involves a root of a constant,
- make sure that the polynomial is over a real field. It cannot yet handle
- roots of symbols.
- Examples
- ========
- >>> from sympy.polys.domains import QQ, RR
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_nth_root
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_nth_root(1 + x + x*y, -3, x, 3)
- 2/9*x**2*y**2 + 4/9*x**2*y + 2/9*x**2 - 1/3*x*y - 1/3*x + 1
- >>> R, x, y = ring('x, y', RR)
- >>> rs_nth_root(3 + x + x*y, 3, x, 2)
- 0.160249952256379*x*y + 0.160249952256379*x + 1.44224957030741
- """
- if n == 0:
- if p == 0:
- raise ValueError('0**0 expression')
- else:
- return p.ring(1)
- if n == 1:
- return rs_trunc(p, x, prec)
- R = p.ring
- index = R.gens.index(x)
- m = min(p, key=lambda k: k[index])[index]
- p = mul_xin(p, index, -m)
- prec -= m
- if _has_constant_term(p - 1, x):
- zm = R.zero_monom
- c = p[zm]
- if R.domain is EX:
- c_expr = c.as_expr()
- const = c_expr**QQ(1, n)
- elif isinstance(c, PolyElement):
- try:
- c_expr = c.as_expr()
- const = R(c_expr**(QQ(1, n)))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- else:
- try: # RealElement doesn't support
- const = R(c**Rational(1, n)) # exponentiation with mpq object
- except ValueError: # as exponent
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- res = rs_nth_root(p/c, n, x, prec)*const
- else:
- res = _nth_root1(p, n, x, prec)
- if m:
- m = QQ(m, n)
- res = mul_xin(res, index, m)
- return res
- def rs_log(p, x, prec):
- """
- The Logarithm of ``p`` modulo ``O(x**prec)``.
- Notes
- =====
- Truncation of ``integral dx p**-1*d p/dx`` is used.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_log
- >>> R, x = ring('x', QQ)
- >>> rs_log(1 + x, x, 8)
- 1/7*x**7 - 1/6*x**6 + 1/5*x**5 - 1/4*x**4 + 1/3*x**3 - 1/2*x**2 + x
- >>> rs_log(x**QQ(3, 2) + 1, x, 5)
- 1/3*x**(9/2) - 1/2*x**3 + x**(3/2)
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_log, p, x, prec)
- R = p.ring
- if p == 1:
- return R.zero
- c = _get_constant_term(p, x)
- if c:
- const = 0
- if c == 1:
- pass
- else:
- c_expr = c.as_expr()
- if R.domain is EX:
- const = log(c_expr)
- elif isinstance(c, PolyElement):
- try:
- const = R(log(c_expr))
- except ValueError:
- R = R.add_gens([log(c_expr)])
- p = p.set_ring(R)
- x = x.set_ring(R)
- c = c.set_ring(R)
- const = R(log(c_expr))
- else:
- try:
- const = R(log(c))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- dlog = p.diff(x)
- dlog = rs_mul(dlog, _series_inversion1(p, x, prec), x, prec - 1)
- return rs_integrate(dlog, x) + const
- else:
- raise NotImplementedError
- def rs_LambertW(p, x, prec):
- """
- Calculate the series expansion of the principal branch of the Lambert W
- function.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_LambertW
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_LambertW(x + x*y, x, 3)
- -x**2*y**2 - 2*x**2*y - x**2 + x*y + x
- See Also
- ========
- LambertW
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_LambertW, p, x, prec)
- R = p.ring
- p1 = R(0)
- if _has_constant_term(p, x):
- raise NotImplementedError("Polynomial must not have constant term in "
- "the series variables")
- if x in R.gens:
- for precx in _giant_steps(prec):
- e = rs_exp(p1, x, precx)
- p2 = rs_mul(e, p1, x, precx) - p
- p3 = rs_mul(e, p1 + 1, x, precx)
- p3 = rs_series_inversion(p3, x, precx)
- tmp = rs_mul(p2, p3, x, precx)
- p1 -= tmp
- return p1
- else:
- raise NotImplementedError
- def _exp1(p, x, prec):
- r"""Helper function for `rs\_exp`. """
- R = p.ring
- p1 = R(1)
- for precx in _giant_steps(prec):
- pt = p - rs_log(p1, x, precx)
- tmp = rs_mul(pt, p1, x, precx)
- p1 += tmp
- return p1
- def rs_exp(p, x, prec):
- """
- Exponentiation of a series modulo ``O(x**prec)``
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_exp
- >>> R, x = ring('x', QQ)
- >>> rs_exp(x**2, x, 7)
- 1/6*x**6 + 1/2*x**4 + x**2 + 1
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_exp, p, x, prec)
- R = p.ring
- c = _get_constant_term(p, x)
- if c:
- if R.domain is EX:
- c_expr = c.as_expr()
- const = exp(c_expr)
- elif isinstance(c, PolyElement):
- try:
- c_expr = c.as_expr()
- const = R(exp(c_expr))
- except ValueError:
- R = R.add_gens([exp(c_expr)])
- p = p.set_ring(R)
- x = x.set_ring(R)
- c = c.set_ring(R)
- const = R(exp(c_expr))
- else:
- try:
- const = R(exp(c))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- p1 = p - c
- # Makes use of SymPy functions to evaluate the values of the cos/sin
- # of the constant term.
- return const*rs_exp(p1, x, prec)
- if len(p) > 20:
- return _exp1(p, x, prec)
- one = R(1)
- n = 1
- c = []
- for k in range(prec):
- c.append(one/n)
- k += 1
- n *= k
- r = rs_series_from_list(p, c, x, prec)
- return r
- def _atan(p, iv, prec):
- """
- Expansion using formula.
- Faster on very small and univariate series.
- """
- R = p.ring
- mo = R(-1)
- c = [-mo]
- p2 = rs_square(p, iv, prec)
- for k in range(1, prec):
- c.append(mo**k/(2*k + 1))
- s = rs_series_from_list(p2, c, iv, prec)
- s = rs_mul(s, p, iv, prec)
- return s
- def rs_atan(p, x, prec):
- """
- The arctangent of a series
- Return the series expansion of the atan of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_atan
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_atan(x + x*y, x, 4)
- -1/3*x**3*y**3 - x**3*y**2 - x**3*y - 1/3*x**3 + x*y + x
- See Also
- ========
- atan
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_atan, p, x, prec)
- R = p.ring
- const = 0
- if _has_constant_term(p, x):
- zm = R.zero_monom
- c = p[zm]
- if R.domain is EX:
- c_expr = c.as_expr()
- const = atan(c_expr)
- elif isinstance(c, PolyElement):
- try:
- c_expr = c.as_expr()
- const = R(atan(c_expr))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- else:
- try:
- const = R(atan(c))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- # Instead of using a closed form formula, we differentiate atan(p) to get
- # `1/(1+p**2) * dp`, whose series expansion is much easier to calculate.
- # Finally we integrate to get back atan
- dp = p.diff(x)
- p1 = rs_square(p, x, prec) + R(1)
- p1 = rs_series_inversion(p1, x, prec - 1)
- p1 = rs_mul(dp, p1, x, prec - 1)
- return rs_integrate(p1, x) + const
- def rs_asin(p, x, prec):
- """
- Arcsine of a series
- Return the series expansion of the asin of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_asin
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_asin(x, x, 8)
- 5/112*x**7 + 3/40*x**5 + 1/6*x**3 + x
- See Also
- ========
- asin
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_asin, p, x, prec)
- if _has_constant_term(p, x):
- raise NotImplementedError("Polynomial must not have constant term in "
- "series variables")
- R = p.ring
- if x in R.gens:
- # get a good value
- if len(p) > 20:
- dp = rs_diff(p, x)
- p1 = 1 - rs_square(p, x, prec - 1)
- p1 = rs_nth_root(p1, -2, x, prec - 1)
- p1 = rs_mul(dp, p1, x, prec - 1)
- return rs_integrate(p1, x)
- one = R(1)
- c = [0, one, 0]
- for k in range(3, prec, 2):
- c.append((k - 2)**2*c[-2]/(k*(k - 1)))
- c.append(0)
- return rs_series_from_list(p, c, x, prec)
- else:
- raise NotImplementedError
- def _tan1(p, x, prec):
- r"""
- Helper function of :func:`rs_tan`.
- Return the series expansion of tan of a univariate series using Newton's
- method. It takes advantage of the fact that series expansion of atan is
- easier than that of tan.
- Consider `f(x) = y - \arctan(x)`
- Let r be a root of f(x) found using Newton's method.
- Then `f(r) = 0`
- Or `y = \arctan(x)` where `x = \tan(y)` as required.
- """
- R = p.ring
- p1 = R(0)
- for precx in _giant_steps(prec):
- tmp = p - rs_atan(p1, x, precx)
- tmp = rs_mul(tmp, 1 + rs_square(p1, x, precx), x, precx)
- p1 += tmp
- return p1
- def rs_tan(p, x, prec):
- """
- Tangent of a series.
- Return the series expansion of the tan of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_tan
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_tan(x + x*y, x, 4)
- 1/3*x**3*y**3 + x**3*y**2 + x**3*y + 1/3*x**3 + x*y + x
- See Also
- ========
- _tan1, tan
- """
- if rs_is_puiseux(p, x):
- r = rs_puiseux(rs_tan, p, x, prec)
- return r
- R = p.ring
- const = 0
- c = _get_constant_term(p, x)
- if c:
- if R.domain is EX:
- c_expr = c.as_expr()
- const = tan(c_expr)
- elif isinstance(c, PolyElement):
- try:
- c_expr = c.as_expr()
- const = R(tan(c_expr))
- except ValueError:
- R = R.add_gens([tan(c_expr, )])
- p = p.set_ring(R)
- x = x.set_ring(R)
- c = c.set_ring(R)
- const = R(tan(c_expr))
- else:
- try:
- const = R(tan(c))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- p1 = p - c
- # Makes use of SymPy functions to evaluate the values of the cos/sin
- # of the constant term.
- t2 = rs_tan(p1, x, prec)
- t = rs_series_inversion(1 - const*t2, x, prec)
- return rs_mul(const + t2, t, x, prec)
- if R.ngens == 1:
- return _tan1(p, x, prec)
- else:
- return rs_fun(p, rs_tan, x, prec)
- def rs_cot(p, x, prec):
- """
- Cotangent of a series
- Return the series expansion of the cot of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_cot
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_cot(x, x, 6)
- -2/945*x**5 - 1/45*x**3 - 1/3*x + x**(-1)
- See Also
- ========
- cot
- """
- # It can not handle series like `p = x + x*y` where the coefficient of the
- # linear term in the series variable is symbolic.
- if rs_is_puiseux(p, x):
- r = rs_puiseux(rs_cot, p, x, prec)
- return r
- i, m = _check_series_var(p, x, 'cot')
- prec1 = prec + 2*m
- c, s = rs_cos_sin(p, x, prec1)
- s = mul_xin(s, i, -m)
- s = rs_series_inversion(s, x, prec1)
- res = rs_mul(c, s, x, prec1)
- res = mul_xin(res, i, -m)
- res = rs_trunc(res, x, prec)
- return res
- def rs_sin(p, x, prec):
- """
- Sine of a series
- Return the series expansion of the sin of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_sin
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_sin(x + x*y, x, 4)
- -1/6*x**3*y**3 - 1/2*x**3*y**2 - 1/2*x**3*y - 1/6*x**3 + x*y + x
- >>> rs_sin(x**QQ(3, 2) + x*y**QQ(7, 5), x, 4)
- -1/2*x**(7/2)*y**(14/5) - 1/6*x**3*y**(21/5) + x**(3/2) + x*y**(7/5)
- See Also
- ========
- sin
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_sin, p, x, prec)
- R = x.ring
- if not p:
- return R(0)
- c = _get_constant_term(p, x)
- if c:
- if R.domain is EX:
- c_expr = c.as_expr()
- t1, t2 = sin(c_expr), cos(c_expr)
- elif isinstance(c, PolyElement):
- try:
- c_expr = c.as_expr()
- t1, t2 = R(sin(c_expr)), R(cos(c_expr))
- except ValueError:
- R = R.add_gens([sin(c_expr), cos(c_expr)])
- p = p.set_ring(R)
- x = x.set_ring(R)
- c = c.set_ring(R)
- t1, t2 = R(sin(c_expr)), R(cos(c_expr))
- else:
- try:
- t1, t2 = R(sin(c)), R(cos(c))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- p1 = p - c
- # Makes use of SymPy cos, sin functions to evaluate the values of the
- # cos/sin of the constant term.
- return rs_sin(p1, x, prec)*t2 + rs_cos(p1, x, prec)*t1
- # Series is calculated in terms of tan as its evaluation is fast.
- if len(p) > 20 and R.ngens == 1:
- t = rs_tan(p/2, x, prec)
- t2 = rs_square(t, x, prec)
- p1 = rs_series_inversion(1 + t2, x, prec)
- return rs_mul(p1, 2*t, x, prec)
- one = R(1)
- n = 1
- c = [0]
- for k in range(2, prec + 2, 2):
- c.append(one/n)
- c.append(0)
- n *= -k*(k + 1)
- return rs_series_from_list(p, c, x, prec)
- def rs_cos(p, x, prec):
- """
- Cosine of a series
- Return the series expansion of the cos of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_cos
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_cos(x + x*y, x, 4)
- -1/2*x**2*y**2 - x**2*y - 1/2*x**2 + 1
- >>> rs_cos(x + x*y, x, 4)/x**QQ(7, 5)
- -1/2*x**(3/5)*y**2 - x**(3/5)*y - 1/2*x**(3/5) + x**(-7/5)
- See Also
- ========
- cos
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_cos, p, x, prec)
- R = p.ring
- c = _get_constant_term(p, x)
- if c:
- if R.domain is EX:
- c_expr = c.as_expr()
- _, _ = sin(c_expr), cos(c_expr)
- elif isinstance(c, PolyElement):
- try:
- c_expr = c.as_expr()
- _, _ = R(sin(c_expr)), R(cos(c_expr))
- except ValueError:
- R = R.add_gens([sin(c_expr), cos(c_expr)])
- p = p.set_ring(R)
- x = x.set_ring(R)
- c = c.set_ring(R)
- else:
- try:
- _, _ = R(sin(c)), R(cos(c))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- p1 = p - c
- # Makes use of SymPy cos, sin functions to evaluate the values of the
- # cos/sin of the constant term.
- p_cos = rs_cos(p1, x, prec)
- p_sin = rs_sin(p1, x, prec)
- R = R.compose(p_cos.ring).compose(p_sin.ring)
- p_cos.set_ring(R)
- p_sin.set_ring(R)
- t1, t2 = R(sin(c_expr)), R(cos(c_expr))
- return p_cos*t2 - p_sin*t1
- # Series is calculated in terms of tan as its evaluation is fast.
- if len(p) > 20 and R.ngens == 1:
- t = rs_tan(p/2, x, prec)
- t2 = rs_square(t, x, prec)
- p1 = rs_series_inversion(1+t2, x, prec)
- return rs_mul(p1, 1 - t2, x, prec)
- one = R(1)
- n = 1
- c = []
- for k in range(2, prec + 2, 2):
- c.append(one/n)
- c.append(0)
- n *= -k*(k - 1)
- return rs_series_from_list(p, c, x, prec)
- def rs_cos_sin(p, x, prec):
- r"""
- Return the tuple ``(rs_cos(p, x, prec)`, `rs_sin(p, x, prec))``.
- Is faster than calling rs_cos and rs_sin separately
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_cos_sin, p, x, prec)
- t = rs_tan(p/2, x, prec)
- t2 = rs_square(t, x, prec)
- p1 = rs_series_inversion(1 + t2, x, prec)
- return (rs_mul(p1, 1 - t2, x, prec), rs_mul(p1, 2*t, x, prec))
- def _atanh(p, x, prec):
- """
- Expansion using formula
- Faster for very small and univariate series
- """
- R = p.ring
- one = R(1)
- c = [one]
- p2 = rs_square(p, x, prec)
- for k in range(1, prec):
- c.append(one/(2*k + 1))
- s = rs_series_from_list(p2, c, x, prec)
- s = rs_mul(s, p, x, prec)
- return s
- def rs_atanh(p, x, prec):
- """
- Hyperbolic arctangent of a series
- Return the series expansion of the atanh of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_atanh
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_atanh(x + x*y, x, 4)
- 1/3*x**3*y**3 + x**3*y**2 + x**3*y + 1/3*x**3 + x*y + x
- See Also
- ========
- atanh
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_atanh, p, x, prec)
- R = p.ring
- const = 0
- if _has_constant_term(p, x):
- zm = R.zero_monom
- c = p[zm]
- if R.domain is EX:
- c_expr = c.as_expr()
- const = atanh(c_expr)
- elif isinstance(c, PolyElement):
- try:
- c_expr = c.as_expr()
- const = R(atanh(c_expr))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- else:
- try:
- const = R(atanh(c))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- # Instead of using a closed form formula, we differentiate atanh(p) to get
- # `1/(1-p**2) * dp`, whose series expansion is much easier to calculate.
- # Finally we integrate to get back atanh
- dp = rs_diff(p, x)
- p1 = - rs_square(p, x, prec) + 1
- p1 = rs_series_inversion(p1, x, prec - 1)
- p1 = rs_mul(dp, p1, x, prec - 1)
- return rs_integrate(p1, x) + const
- def rs_sinh(p, x, prec):
- """
- Hyperbolic sine of a series
- Return the series expansion of the sinh of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_sinh
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_sinh(x + x*y, x, 4)
- 1/6*x**3*y**3 + 1/2*x**3*y**2 + 1/2*x**3*y + 1/6*x**3 + x*y + x
- See Also
- ========
- sinh
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_sinh, p, x, prec)
- t = rs_exp(p, x, prec)
- t1 = rs_series_inversion(t, x, prec)
- return (t - t1)/2
- def rs_cosh(p, x, prec):
- """
- Hyperbolic cosine of a series
- Return the series expansion of the cosh of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_cosh
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_cosh(x + x*y, x, 4)
- 1/2*x**2*y**2 + x**2*y + 1/2*x**2 + 1
- See Also
- ========
- cosh
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_cosh, p, x, prec)
- t = rs_exp(p, x, prec)
- t1 = rs_series_inversion(t, x, prec)
- return (t + t1)/2
- def _tanh(p, x, prec):
- r"""
- Helper function of :func:`rs_tanh`
- Return the series expansion of tanh of a univariate series using Newton's
- method. It takes advantage of the fact that series expansion of atanh is
- easier than that of tanh.
- See Also
- ========
- _tanh
- """
- R = p.ring
- p1 = R(0)
- for precx in _giant_steps(prec):
- tmp = p - rs_atanh(p1, x, precx)
- tmp = rs_mul(tmp, 1 - rs_square(p1, x, prec), x, precx)
- p1 += tmp
- return p1
- def rs_tanh(p, x, prec):
- """
- Hyperbolic tangent of a series
- Return the series expansion of the tanh of ``p``, about 0.
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_tanh
- >>> R, x, y = ring('x, y', QQ)
- >>> rs_tanh(x + x*y, x, 4)
- -1/3*x**3*y**3 - x**3*y**2 - x**3*y - 1/3*x**3 + x*y + x
- See Also
- ========
- tanh
- """
- if rs_is_puiseux(p, x):
- return rs_puiseux(rs_tanh, p, x, prec)
- R = p.ring
- const = 0
- if _has_constant_term(p, x):
- zm = R.zero_monom
- c = p[zm]
- if R.domain is EX:
- c_expr = c.as_expr()
- const = tanh(c_expr)
- elif isinstance(c, PolyElement):
- try:
- c_expr = c.as_expr()
- const = R(tanh(c_expr))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- else:
- try:
- const = R(tanh(c))
- except ValueError:
- raise DomainError("The given series cannot be expanded in "
- "this domain.")
- p1 = p - c
- t1 = rs_tanh(p1, x, prec)
- t = rs_series_inversion(1 + const*t1, x, prec)
- return rs_mul(const + t1, t, x, prec)
- if R.ngens == 1:
- return _tanh(p, x, prec)
- else:
- return rs_fun(p, _tanh, x, prec)
- def rs_newton(p, x, prec):
- """
- Compute the truncated Newton sum of the polynomial ``p``
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_newton
- >>> R, x = ring('x', QQ)
- >>> p = x**2 - 2
- >>> rs_newton(p, x, 5)
- 8*x**4 + 4*x**2 + 2
- """
- deg = p.degree()
- p1 = _invert_monoms(p)
- p2 = rs_series_inversion(p1, x, prec)
- p3 = rs_mul(p1.diff(x), p2, x, prec)
- res = deg - p3*x
- return res
- def rs_hadamard_exp(p1, inverse=False):
- """
- Return ``sum f_i/i!*x**i`` from ``sum f_i*x**i``,
- where ``x`` is the first variable.
- If ``invers=True`` return ``sum f_i*i!*x**i``
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_hadamard_exp
- >>> R, x = ring('x', QQ)
- >>> p = 1 + x + x**2 + x**3
- >>> rs_hadamard_exp(p)
- 1/6*x**3 + 1/2*x**2 + x + 1
- """
- R = p1.ring
- if R.domain != QQ:
- raise NotImplementedError
- p = R.zero
- if not inverse:
- for exp1, v1 in p1.items():
- p[exp1] = v1/int(ifac(exp1[0]))
- else:
- for exp1, v1 in p1.items():
- p[exp1] = v1*int(ifac(exp1[0]))
- return p
- def rs_compose_add(p1, p2):
- """
- compute the composed sum ``prod(p2(x - beta) for beta root of p1)``
- Examples
- ========
- >>> from sympy.polys.domains import QQ
- >>> from sympy.polys.rings import ring
- >>> from sympy.polys.ring_series import rs_compose_add
- >>> R, x = ring('x', QQ)
- >>> f = x**2 - 2
- >>> g = x**2 - 3
- >>> rs_compose_add(f, g)
- x**4 - 10*x**2 + 1
- References
- ==========
- .. [1] A. Bostan, P. Flajolet, B. Salvy and E. Schost
- "Fast Computation with Two Algebraic Numbers",
- (2002) Research Report 4579, Institut
- National de Recherche en Informatique et en Automatique
- """
- R = p1.ring
- x = R.gens[0]
- prec = p1.degree()*p2.degree() + 1
- np1 = rs_newton(p1, x, prec)
- np1e = rs_hadamard_exp(np1)
- np2 = rs_newton(p2, x, prec)
- np2e = rs_hadamard_exp(np2)
- np3e = rs_mul(np1e, np2e, x, prec)
- np3 = rs_hadamard_exp(np3e, True)
- np3a = (np3[(0,)] - np3)/x
- q = rs_integrate(np3a, x)
- q = rs_exp(q, x, prec)
- q = _invert_monoms(q)
- q = q.primitive()[1]
- dp = p1.degree()*p2.degree() - q.degree()
- # `dp` is the multiplicity of the zeroes of the resultant;
- # these zeroes are missed in this computation so they are put here.
- # if p1 and p2 are monic irreducible polynomials,
- # there are zeroes in the resultant
- # if and only if p1 = p2 ; in fact in that case p1 and p2 have a
- # root in common, so gcd(p1, p2) != 1; being p1 and p2 irreducible
- # this means p1 = p2
- if dp:
- q = q*x**dp
- return q
- _convert_func = {
- 'sin': 'rs_sin',
- 'cos': 'rs_cos',
- 'exp': 'rs_exp',
- 'tan': 'rs_tan',
- 'log': 'rs_log'
- }
- def rs_min_pow(expr, series_rs, a):
- """Find the minimum power of `a` in the series expansion of expr"""
- series = 0
- n = 2
- while series == 0:
- series = _rs_series(expr, series_rs, a, n)
- n *= 2
- R = series.ring
- a = R(a)
- i = R.gens.index(a)
- return min(series, key=lambda t: t[i])[i]
- def _rs_series(expr, series_rs, a, prec):
- # TODO Use _parallel_dict_from_expr instead of sring as sring is
- # inefficient. For details, read the todo in sring.
- args = expr.args
- R = series_rs.ring
- # expr does not contain any function to be expanded
- if not any(arg.has(Function) for arg in args) and not expr.is_Function:
- return series_rs
- if not expr.has(a):
- return series_rs
- elif expr.is_Function:
- arg = args[0]
- if len(args) > 1:
- raise NotImplementedError
- R1, series = sring(arg, domain=QQ, expand=False, series=True)
- series_inner = _rs_series(arg, series, a, prec)
- # Why do we need to compose these three rings?
- #
- # We want to use a simple domain (like ``QQ`` or ``RR``) but they don't
- # support symbolic coefficients. We need a ring that for example lets
- # us have `sin(1)` and `cos(1)` as coefficients if we are expanding
- # `sin(x + 1)`. The ``EX`` domain allows all symbolic coefficients, but
- # that makes it very complex and hence slow.
- #
- # To solve this problem, we add only those symbolic elements as
- # generators to our ring, that we need. Here, series_inner might
- # involve terms like `sin(4)`, `exp(a)`, etc, which are not there in
- # R1 or R. Hence, we compose these three rings to create one that has
- # the generators of all three.
- R = R.compose(R1).compose(series_inner.ring)
- series_inner = series_inner.set_ring(R)
- series = eval(_convert_func[str(expr.func)])(series_inner,
- R(a), prec)
- return series
- elif expr.is_Mul:
- n = len(args)
- for arg in args: # XXX Looks redundant
- if not arg.is_Number:
- R1, _ = sring(arg, expand=False, series=True)
- R = R.compose(R1)
- min_pows = list(map(rs_min_pow, args, [R(arg) for arg in args],
- [a]*len(args)))
- sum_pows = sum(min_pows)
- series = R(1)
- for i in range(n):
- _series = _rs_series(args[i], R(args[i]), a, prec - sum_pows +
- min_pows[i])
- R = R.compose(_series.ring)
- _series = _series.set_ring(R)
- series = series.set_ring(R)
- series *= _series
- series = rs_trunc(series, R(a), prec)
- return series
- elif expr.is_Add:
- n = len(args)
- series = R(0)
- for i in range(n):
- _series = _rs_series(args[i], R(args[i]), a, prec)
- R = R.compose(_series.ring)
- _series = _series.set_ring(R)
- series = series.set_ring(R)
- series += _series
- return series
- elif expr.is_Pow:
- R1, _ = sring(expr.base, domain=QQ, expand=False, series=True)
- R = R.compose(R1)
- series_inner = _rs_series(expr.base, R(expr.base), a, prec)
- return rs_pow(series_inner, expr.exp, series_inner.ring(a), prec)
- # The `is_constant` method is buggy hence we check it at the end.
- # See issue #9786 for details.
- elif isinstance(expr, Expr) and expr.is_constant():
- return sring(expr, domain=QQ, expand=False, series=True)[1]
- else:
- raise NotImplementedError
- def rs_series(expr, a, prec):
- """Return the series expansion of an expression about 0.
- Parameters
- ==========
- expr : :class:`Expr`
- a : :class:`Symbol` with respect to which expr is to be expanded
- prec : order of the series expansion
- Currently supports multivariate Taylor series expansion. This is much
- faster that SymPy's series method as it uses sparse polynomial operations.
- It automatically creates the simplest ring required to represent the series
- expansion through repeated calls to sring.
- Examples
- ========
- >>> from sympy.polys.ring_series import rs_series
- >>> from sympy import sin, cos, exp, tan, symbols, QQ
- >>> a, b, c = symbols('a, b, c')
- >>> rs_series(sin(a) + exp(a), a, 5)
- 1/24*a**4 + 1/2*a**2 + 2*a + 1
- >>> series = rs_series(tan(a + b)*cos(a + c), a, 2)
- >>> series.as_expr()
- -a*sin(c)*tan(b) + a*cos(c)*tan(b)**2 + a*cos(c) + cos(c)*tan(b)
- >>> series = rs_series(exp(a**QQ(1,3) + a**QQ(2, 5)), a, 1)
- >>> series.as_expr()
- a**(11/15) + a**(4/5)/2 + a**(2/5) + a**(2/3)/2 + a**(1/3) + 1
- """
- R, series = sring(expr, domain=QQ, expand=False, series=True)
- if a not in R.symbols:
- R = R.add_gens([a, ])
- series = series.set_ring(R)
- series = _rs_series(expr, series, a, prec)
- R = series.ring
- gen = R(a)
- prec_got = series.degree(gen) + 1
- if prec_got >= prec:
- return rs_trunc(series, gen, prec)
- else:
- # increase the requested number of terms to get the desired
- # number keep increasing (up to 9) until the received order
- # is different than the original order and then predict how
- # many additional terms are needed
- for more in range(1, 9):
- p1 = _rs_series(expr, series, a, prec=prec + more)
- gen = gen.set_ring(p1.ring)
- new_prec = p1.degree(gen) + 1
- if new_prec != prec_got:
- prec_do = ceiling(prec + (prec - prec_got)*more/(new_prec -
- prec_got))
- p1 = _rs_series(expr, series, a, prec=prec_do)
- while p1.degree(gen) + 1 < prec:
- p1 = _rs_series(expr, series, a, prec=prec_do)
- gen = gen.set_ring(p1.ring)
- prec_do *= 2
- break
- else:
- break
- else:
- raise ValueError('Could not calculate %s terms for %s'
- % (str(prec), expr))
- return rs_trunc(p1, gen, prec)
|