1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858 |
- """
- The Risch Algorithm for transcendental function integration.
- The core algorithms for the Risch algorithm are here. The subproblem
- algorithms are in the rde.py and prde.py files for the Risch
- Differential Equation solver and the parametric problems solvers,
- respectively. All important information concerning the differential extension
- for an integrand is stored in a DifferentialExtension object, which in the code
- is usually called DE. Throughout the code and Inside the DifferentialExtension
- object, the conventions/attribute names are that the base domain is QQ and each
- differential extension is x, t0, t1, ..., tn-1 = DE.t. DE.x is the variable of
- integration (Dx == 1), DE.D is a list of the derivatives of
- x, t1, t2, ..., tn-1 = t, DE.T is the list [x, t1, t2, ..., tn-1], DE.t is the
- outer-most variable of the differential extension at the given level (the level
- can be adjusted using DE.increment_level() and DE.decrement_level()),
- k is the field C(x, t0, ..., tn-2), where C is the constant field. The
- numerator of a fraction is denoted by a and the denominator by
- d. If the fraction is named f, fa == numer(f) and fd == denom(f).
- Fractions are returned as tuples (fa, fd). DE.d and DE.t are used to
- represent the topmost derivation and extension variable, respectively.
- The docstring of a function signifies whether an argument is in k[t], in
- which case it will just return a Poly in t, or in k(t), in which case it
- will return the fraction (fa, fd). Other variable names probably come
- from the names used in Bronstein's book.
- """
- from types import GeneratorType
- from functools import reduce
- from sympy.core.function import Lambda
- from sympy.core.mul import Mul
- from sympy.core.numbers import ilcm, I, oo
- from sympy.core.power import Pow
- from sympy.core.relational import Ne
- from sympy.core.singleton import S
- from sympy.core.sorting import ordered, default_sort_key
- from sympy.core.symbol import Dummy, Symbol
- from sympy.functions.elementary.exponential import log, exp
- from sympy.functions.elementary.hyperbolic import (cosh, coth, sinh,
- tanh)
- from sympy.functions.elementary.piecewise import Piecewise
- from sympy.functions.elementary.trigonometric import (atan, sin, cos,
- tan, acot, cot, asin, acos)
- from .integrals import integrate, Integral
- from .heurisch import _symbols
- from sympy.polys.polyerrors import DomainError, PolynomialError
- from sympy.polys.polytools import (real_roots, cancel, Poly, gcd,
- reduced)
- from sympy.polys.rootoftools import RootSum
- from sympy.utilities.iterables import numbered_symbols
- def integer_powers(exprs):
- """
- Rewrites a list of expressions as integer multiples of each other.
- Explanation
- ===========
- For example, if you have [x, x/2, x**2 + 1, 2*x/3], then you can rewrite
- this as [(x/6) * 6, (x/6) * 3, (x**2 + 1) * 1, (x/6) * 4]. This is useful
- in the Risch integration algorithm, where we must write exp(x) + exp(x/2)
- as (exp(x/2))**2 + exp(x/2), but not as exp(x) + sqrt(exp(x)) (this is
- because only the transcendental case is implemented and we therefore cannot
- integrate algebraic extensions). The integer multiples returned by this
- function for each term are the smallest possible (their content equals 1).
- Returns a list of tuples where the first element is the base term and the
- second element is a list of `(item, factor)` terms, where `factor` is the
- integer multiplicative factor that must multiply the base term to obtain
- the original item.
- The easiest way to understand this is to look at an example:
- >>> from sympy.abc import x
- >>> from sympy.integrals.risch import integer_powers
- >>> integer_powers([x, x/2, x**2 + 1, 2*x/3])
- [(x/6, [(x, 6), (x/2, 3), (2*x/3, 4)]), (x**2 + 1, [(x**2 + 1, 1)])]
- We can see how this relates to the example at the beginning of the
- docstring. It chose x/6 as the first base term. Then, x can be written as
- (x/2) * 2, so we get (0, 2), and so on. Now only element (x**2 + 1)
- remains, and there are no other terms that can be written as a rational
- multiple of that, so we get that it can be written as (x**2 + 1) * 1.
- """
- # Here is the strategy:
- # First, go through each term and determine if it can be rewritten as a
- # rational multiple of any of the terms gathered so far.
- # cancel(a/b).is_Rational is sufficient for this. If it is a multiple, we
- # add its multiple to the dictionary.
- terms = {}
- for term in exprs:
- for trm, trm_list in terms.items():
- a = cancel(term/trm)
- if a.is_Rational:
- trm_list.append((term, a))
- break
- else:
- terms[term] = [(term, S.One)]
- # After we have done this, we have all the like terms together, so we just
- # need to find a common denominator so that we can get the base term and
- # integer multiples such that each term can be written as an integer
- # multiple of the base term, and the content of the integers is 1.
- newterms = {}
- for term, term_list in terms.items():
- common_denom = reduce(ilcm, [i.as_numer_denom()[1] for _, i in
- term_list])
- newterm = term/common_denom
- newmults = [(i, j*common_denom) for i, j in term_list]
- newterms[newterm] = newmults
- return sorted(iter(newterms.items()), key=lambda item: item[0].sort_key())
- class DifferentialExtension:
- """
- A container for all the information relating to a differential extension.
- Explanation
- ===========
- The attributes of this object are (see also the docstring of __init__):
- - f: The original (Expr) integrand.
- - x: The variable of integration.
- - T: List of variables in the extension.
- - D: List of derivations in the extension; corresponds to the elements of T.
- - fa: Poly of the numerator of the integrand.
- - fd: Poly of the denominator of the integrand.
- - Tfuncs: Lambda() representations of each element of T (except for x).
- For back-substitution after integration.
- - backsubs: A (possibly empty) list of further substitutions to be made on
- the final integral to make it look more like the integrand.
- - exts:
- - extargs:
- - cases: List of string representations of the cases of T.
- - t: The top level extension variable, as defined by the current level
- (see level below).
- - d: The top level extension derivation, as defined by the current
- derivation (see level below).
- - case: The string representation of the case of self.d.
- (Note that self.T and self.D will always contain the complete extension,
- regardless of the level. Therefore, you should ALWAYS use DE.t and DE.d
- instead of DE.T[-1] and DE.D[-1]. If you want to have a list of the
- derivations or variables only up to the current level, use
- DE.D[:len(DE.D) + DE.level + 1] and DE.T[:len(DE.T) + DE.level + 1]. Note
- that, in particular, the derivation() function does this.)
- The following are also attributes, but will probably not be useful other
- than in internal use:
- - newf: Expr form of fa/fd.
- - level: The number (between -1 and -len(self.T)) such that
- self.T[self.level] == self.t and self.D[self.level] == self.d.
- Use the methods self.increment_level() and self.decrement_level() to change
- the current level.
- """
- # __slots__ is defined mainly so we can iterate over all the attributes
- # of the class easily (the memory use doesn't matter too much, since we
- # only create one DifferentialExtension per integration). Also, it's nice
- # to have a safeguard when debugging.
- __slots__ = ('f', 'x', 'T', 'D', 'fa', 'fd', 'Tfuncs', 'backsubs',
- 'exts', 'extargs', 'cases', 'case', 't', 'd', 'newf', 'level',
- 'ts', 'dummy')
- def __init__(self, f=None, x=None, handle_first='log', dummy=False, extension=None, rewrite_complex=None):
- """
- Tries to build a transcendental extension tower from ``f`` with respect to ``x``.
- Explanation
- ===========
- If it is successful, creates a DifferentialExtension object with, among
- others, the attributes fa, fd, D, T, Tfuncs, and backsubs such that
- fa and fd are Polys in T[-1] with rational coefficients in T[:-1],
- fa/fd == f, and D[i] is a Poly in T[i] with rational coefficients in
- T[:i] representing the derivative of T[i] for each i from 1 to len(T).
- Tfuncs is a list of Lambda objects for back replacing the functions
- after integrating. Lambda() is only used (instead of lambda) to make
- them easier to test and debug. Note that Tfuncs corresponds to the
- elements of T, except for T[0] == x, but they should be back-substituted
- in reverse order. backsubs is a (possibly empty) back-substitution list
- that should be applied on the completed integral to make it look more
- like the original integrand.
- If it is unsuccessful, it raises NotImplementedError.
- You can also create an object by manually setting the attributes as a
- dictionary to the extension keyword argument. You must include at least
- D. Warning, any attribute that is not given will be set to None. The
- attributes T, t, d, cases, case, x, and level are set automatically and
- do not need to be given. The functions in the Risch Algorithm will NOT
- check to see if an attribute is None before using it. This also does not
- check to see if the extension is valid (non-algebraic) or even if it is
- self-consistent. Therefore, this should only be used for
- testing/debugging purposes.
- """
- # XXX: If you need to debug this function, set the break point here
- if extension:
- if 'D' not in extension:
- raise ValueError("At least the key D must be included with "
- "the extension flag to DifferentialExtension.")
- for attr in extension:
- setattr(self, attr, extension[attr])
- self._auto_attrs()
- return
- elif f is None or x is None:
- raise ValueError("Either both f and x or a manual extension must "
- "be given.")
- if handle_first not in ('log', 'exp'):
- raise ValueError("handle_first must be 'log' or 'exp', not %s." %
- str(handle_first))
- # f will be the original function, self.f might change if we reset
- # (e.g., we pull out a constant from an exponential)
- self.f = f
- self.x = x
- # setting the default value 'dummy'
- self.dummy = dummy
- self.reset()
- exp_new_extension, log_new_extension = True, True
- # case of 'automatic' choosing
- if rewrite_complex is None:
- rewrite_complex = I in self.f.atoms()
- if rewrite_complex:
- rewritables = {
- (sin, cos, cot, tan, sinh, cosh, coth, tanh): exp,
- (asin, acos, acot, atan): log,
- }
- # rewrite the trigonometric components
- for candidates, rule in rewritables.items():
- self.newf = self.newf.rewrite(candidates, rule)
- self.newf = cancel(self.newf)
- else:
- if any(i.has(x) for i in self.f.atoms(sin, cos, tan, atan, asin, acos)):
- raise NotImplementedError("Trigonometric extensions are not "
- "supported (yet!)")
- exps = set()
- pows = set()
- numpows = set()
- sympows = set()
- logs = set()
- symlogs = set()
- while True:
- if self.newf.is_rational_function(*self.T):
- break
- if not exp_new_extension and not log_new_extension:
- # We couldn't find a new extension on the last pass, so I guess
- # we can't do it.
- raise NotImplementedError("Couldn't find an elementary "
- "transcendental extension for %s. Try using a " % str(f) +
- "manual extension with the extension flag.")
- exps, pows, numpows, sympows, log_new_extension = \
- self._rewrite_exps_pows(exps, pows, numpows, sympows, log_new_extension)
- logs, symlogs = self._rewrite_logs(logs, symlogs)
- if handle_first == 'exp' or not log_new_extension:
- exp_new_extension = self._exp_part(exps)
- if exp_new_extension is None:
- # reset and restart
- self.f = self.newf
- self.reset()
- exp_new_extension = True
- continue
- if handle_first == 'log' or not exp_new_extension:
- log_new_extension = self._log_part(logs)
- self.fa, self.fd = frac_in(self.newf, self.t)
- self._auto_attrs()
- return
- def __getattr__(self, attr):
- # Avoid AttributeErrors when debugging
- if attr not in self.__slots__:
- raise AttributeError("%s has no attribute %s" % (repr(self), repr(attr)))
- return None
- def _rewrite_exps_pows(self, exps, pows, numpows,
- sympows, log_new_extension):
- """
- Rewrite exps/pows for better processing.
- """
- from .prde import is_deriv_k
- # Pre-preparsing.
- #################
- # Get all exp arguments, so we can avoid ahead of time doing
- # something like t1 = exp(x), t2 = exp(x/2) == sqrt(t1).
- # Things like sqrt(exp(x)) do not automatically simplify to
- # exp(x/2), so they will be viewed as algebraic. The easiest way
- # to handle this is to convert all instances of (a**b)**Rational
- # to a**(Rational*b) before doing anything else. Note that the
- # _exp_part code can generate terms of this form, so we do need to
- # do this at each pass (or else modify it to not do that).
- ratpows = [i for i in self.newf.atoms(Pow).union(self.newf.atoms(exp))
- if (i.base.is_Pow or isinstance(i.base, exp) and i.exp.is_Rational)]
- ratpows_repl = [
- (i, i.base.base**(i.exp*i.base.exp)) for i in ratpows]
- self.backsubs += [(j, i) for i, j in ratpows_repl]
- self.newf = self.newf.xreplace(dict(ratpows_repl))
- # To make the process deterministic, the args are sorted
- # so that functions with smaller op-counts are processed first.
- # Ties are broken with the default_sort_key.
- # XXX Although the method is deterministic no additional work
- # has been done to guarantee that the simplest solution is
- # returned and that it would be affected be using different
- # variables. Though it is possible that this is the case
- # one should know that it has not been done intentionally, so
- # further improvements may be possible.
- # TODO: This probably doesn't need to be completely recomputed at
- # each pass.
- exps = update_sets(exps, self.newf.atoms(exp),
- lambda i: i.exp.is_rational_function(*self.T) and
- i.exp.has(*self.T))
- pows = update_sets(pows, self.newf.atoms(Pow),
- lambda i: i.exp.is_rational_function(*self.T) and
- i.exp.has(*self.T))
- numpows = update_sets(numpows, set(pows),
- lambda i: not i.base.has(*self.T))
- sympows = update_sets(sympows, set(pows) - set(numpows),
- lambda i: i.base.is_rational_function(*self.T) and
- not i.exp.is_Integer)
- # The easiest way to deal with non-base E powers is to convert them
- # into base E, integrate, and then convert back.
- for i in ordered(pows):
- old = i
- new = exp(i.exp*log(i.base))
- # If exp is ever changed to automatically reduce exp(x*log(2))
- # to 2**x, then this will break. The solution is to not change
- # exp to do that :)
- if i in sympows:
- if i.exp.is_Rational:
- raise NotImplementedError("Algebraic extensions are "
- "not supported (%s)." % str(i))
- # We can add a**b only if log(a) in the extension, because
- # a**b == exp(b*log(a)).
- basea, based = frac_in(i.base, self.t)
- A = is_deriv_k(basea, based, self)
- if A is None:
- # Nonelementary monomial (so far)
- # TODO: Would there ever be any benefit from just
- # adding log(base) as a new monomial?
- # ANSWER: Yes, otherwise we can't integrate x**x (or
- # rather prove that it has no elementary integral)
- # without first manually rewriting it as exp(x*log(x))
- self.newf = self.newf.xreplace({old: new})
- self.backsubs += [(new, old)]
- log_new_extension = self._log_part([log(i.base)])
- exps = update_sets(exps, self.newf.atoms(exp), lambda i:
- i.exp.is_rational_function(*self.T) and i.exp.has(*self.T))
- continue
- ans, u, const = A
- newterm = exp(i.exp*(log(const) + u))
- # Under the current implementation, exp kills terms
- # only if they are of the form a*log(x), where a is a
- # Number. This case should have already been killed by the
- # above tests. Again, if this changes to kill more than
- # that, this will break, which maybe is a sign that you
- # shouldn't be changing that. Actually, if anything, this
- # auto-simplification should be removed. See
- # http://groups.google.com/group/sympy/browse_thread/thread/a61d48235f16867f
- self.newf = self.newf.xreplace({i: newterm})
- elif i not in numpows:
- continue
- else:
- # i in numpows
- newterm = new
- # TODO: Just put it in self.Tfuncs
- self.backsubs.append((new, old))
- self.newf = self.newf.xreplace({old: newterm})
- exps.append(newterm)
- return exps, pows, numpows, sympows, log_new_extension
- def _rewrite_logs(self, logs, symlogs):
- """
- Rewrite logs for better processing.
- """
- atoms = self.newf.atoms(log)
- logs = update_sets(logs, atoms,
- lambda i: i.args[0].is_rational_function(*self.T) and
- i.args[0].has(*self.T))
- symlogs = update_sets(symlogs, atoms,
- lambda i: i.has(*self.T) and i.args[0].is_Pow and
- i.args[0].base.is_rational_function(*self.T) and
- not i.args[0].exp.is_Integer)
- # We can handle things like log(x**y) by converting it to y*log(x)
- # This will fix not only symbolic exponents of the argument, but any
- # non-Integer exponent, like log(sqrt(x)). The exponent can also
- # depend on x, like log(x**x).
- for i in ordered(symlogs):
- # Unlike in the exponential case above, we do not ever
- # potentially add new monomials (above we had to add log(a)).
- # Therefore, there is no need to run any is_deriv functions
- # here. Just convert log(a**b) to b*log(a) and let
- # log_new_extension() handle it from there.
- lbase = log(i.args[0].base)
- logs.append(lbase)
- new = i.args[0].exp*lbase
- self.newf = self.newf.xreplace({i: new})
- self.backsubs.append((new, i))
- # remove any duplicates
- logs = sorted(set(logs), key=default_sort_key)
- return logs, symlogs
- def _auto_attrs(self):
- """
- Set attributes that are generated automatically.
- """
- if not self.T:
- # i.e., when using the extension flag and T isn't given
- self.T = [i.gen for i in self.D]
- if not self.x:
- self.x = self.T[0]
- self.cases = [get_case(d, t) for d, t in zip(self.D, self.T)]
- self.level = -1
- self.t = self.T[self.level]
- self.d = self.D[self.level]
- self.case = self.cases[self.level]
- def _exp_part(self, exps):
- """
- Try to build an exponential extension.
- Returns
- =======
- Returns True if there was a new extension, False if there was no new
- extension but it was able to rewrite the given exponentials in terms
- of the existing extension, and None if the entire extension building
- process should be restarted. If the process fails because there is no
- way around an algebraic extension (e.g., exp(log(x)/2)), it will raise
- NotImplementedError.
- """
- from .prde import is_log_deriv_k_t_radical
- new_extension = False
- restart = False
- expargs = [i.exp for i in exps]
- ip = integer_powers(expargs)
- for arg, others in ip:
- # Minimize potential problems with algebraic substitution
- others.sort(key=lambda i: i[1])
- arga, argd = frac_in(arg, self.t)
- A = is_log_deriv_k_t_radical(arga, argd, self)
- if A is not None:
- ans, u, n, const = A
- # if n is 1 or -1, it's algebraic, but we can handle it
- if n == -1:
- # This probably will never happen, because
- # Rational.as_numer_denom() returns the negative term in
- # the numerator. But in case that changes, reduce it to
- # n == 1.
- n = 1
- u **= -1
- const *= -1
- ans = [(i, -j) for i, j in ans]
- if n == 1:
- # Example: exp(x + x**2) over QQ(x, exp(x), exp(x**2))
- self.newf = self.newf.xreplace({exp(arg): exp(const)*Mul(*[
- u**power for u, power in ans])})
- self.newf = self.newf.xreplace({exp(p*exparg):
- exp(const*p) * Mul(*[u**power for u, power in ans])
- for exparg, p in others})
- # TODO: Add something to backsubs to put exp(const*p)
- # back together.
- continue
- else:
- # Bad news: we have an algebraic radical. But maybe we
- # could still avoid it by choosing a different extension.
- # For example, integer_powers() won't handle exp(x/2 + 1)
- # over QQ(x, exp(x)), but if we pull out the exp(1), it
- # will. Or maybe we have exp(x + x**2/2), over
- # QQ(x, exp(x), exp(x**2)), which is exp(x)*sqrt(exp(x**2)),
- # but if we use QQ(x, exp(x), exp(x**2/2)), then they will
- # all work.
- #
- # So here is what we do: If there is a non-zero const, pull
- # it out and retry. Also, if len(ans) > 1, then rewrite
- # exp(arg) as the product of exponentials from ans, and
- # retry that. If const == 0 and len(ans) == 1, then we
- # assume that it would have been handled by either
- # integer_powers() or n == 1 above if it could be handled,
- # so we give up at that point. For example, you can never
- # handle exp(log(x)/2) because it equals sqrt(x).
- if const or len(ans) > 1:
- rad = Mul(*[term**(power/n) for term, power in ans])
- self.newf = self.newf.xreplace({exp(p*exparg):
- exp(const*p)*rad for exparg, p in others})
- self.newf = self.newf.xreplace(dict(list(zip(reversed(self.T),
- reversed([f(self.x) for f in self.Tfuncs])))))
- restart = True
- break
- else:
- # TODO: give algebraic dependence in error string
- raise NotImplementedError("Cannot integrate over "
- "algebraic extensions.")
- else:
- arga, argd = frac_in(arg, self.t)
- darga = (argd*derivation(Poly(arga, self.t), self) -
- arga*derivation(Poly(argd, self.t), self))
- dargd = argd**2
- darga, dargd = darga.cancel(dargd, include=True)
- darg = darga.as_expr()/dargd.as_expr()
- self.t = next(self.ts)
- self.T.append(self.t)
- self.extargs.append(arg)
- self.exts.append('exp')
- self.D.append(darg.as_poly(self.t, expand=False)*Poly(self.t,
- self.t, expand=False))
- if self.dummy:
- i = Dummy("i")
- else:
- i = Symbol('i')
- self.Tfuncs += [Lambda(i, exp(arg.subs(self.x, i)))]
- self.newf = self.newf.xreplace(
- {exp(exparg): self.t**p for exparg, p in others})
- new_extension = True
- if restart:
- return None
- return new_extension
- def _log_part(self, logs):
- """
- Try to build a logarithmic extension.
- Returns
- =======
- Returns True if there was a new extension and False if there was no new
- extension but it was able to rewrite the given logarithms in terms
- of the existing extension. Unlike with exponential extensions, there
- is no way that a logarithm is not transcendental over and cannot be
- rewritten in terms of an already existing extension in a non-algebraic
- way, so this function does not ever return None or raise
- NotImplementedError.
- """
- from .prde import is_deriv_k
- new_extension = False
- logargs = [i.args[0] for i in logs]
- for arg in ordered(logargs):
- # The log case is easier, because whenever a logarithm is algebraic
- # over the base field, it is of the form a1*t1 + ... an*tn + c,
- # which is a polynomial, so we can just replace it with that.
- # In other words, we don't have to worry about radicals.
- arga, argd = frac_in(arg, self.t)
- A = is_deriv_k(arga, argd, self)
- if A is not None:
- ans, u, const = A
- newterm = log(const) + u
- self.newf = self.newf.xreplace({log(arg): newterm})
- continue
- else:
- arga, argd = frac_in(arg, self.t)
- darga = (argd*derivation(Poly(arga, self.t), self) -
- arga*derivation(Poly(argd, self.t), self))
- dargd = argd**2
- darg = darga.as_expr()/dargd.as_expr()
- self.t = next(self.ts)
- self.T.append(self.t)
- self.extargs.append(arg)
- self.exts.append('log')
- self.D.append(cancel(darg.as_expr()/arg).as_poly(self.t,
- expand=False))
- if self.dummy:
- i = Dummy("i")
- else:
- i = Symbol('i')
- self.Tfuncs += [Lambda(i, log(arg.subs(self.x, i)))]
- self.newf = self.newf.xreplace({log(arg): self.t})
- new_extension = True
- return new_extension
- @property
- def _important_attrs(self):
- """
- Returns some of the more important attributes of self.
- Explanation
- ===========
- Used for testing and debugging purposes.
- The attributes are (fa, fd, D, T, Tfuncs, backsubs,
- exts, extargs).
- """
- return (self.fa, self.fd, self.D, self.T, self.Tfuncs,
- self.backsubs, self.exts, self.extargs)
- # NOTE: this printing doesn't follow the Python's standard
- # eval(repr(DE)) == DE, where DE is the DifferentialExtension object,
- # also this printing is supposed to contain all the important
- # attributes of a DifferentialExtension object
- def __repr__(self):
- # no need to have GeneratorType object printed in it
- r = [(attr, getattr(self, attr)) for attr in self.__slots__
- if not isinstance(getattr(self, attr), GeneratorType)]
- return self.__class__.__name__ + '(dict(%r))' % (r)
- # fancy printing of DifferentialExtension object
- def __str__(self):
- return (self.__class__.__name__ + '({fa=%s, fd=%s, D=%s})' %
- (self.fa, self.fd, self.D))
- # should only be used for debugging purposes, internally
- # f1 = f2 = log(x) at different places in code execution
- # may return D1 != D2 as True, since 'level' or other attribute
- # may differ
- def __eq__(self, other):
- for attr in self.__class__.__slots__:
- d1, d2 = getattr(self, attr), getattr(other, attr)
- if not (isinstance(d1, GeneratorType) or d1 == d2):
- return False
- return True
- def reset(self):
- """
- Reset self to an initial state. Used by __init__.
- """
- self.t = self.x
- self.T = [self.x]
- self.D = [Poly(1, self.x)]
- self.level = -1
- self.exts = [None]
- self.extargs = [None]
- if self.dummy:
- self.ts = numbered_symbols('t', cls=Dummy)
- else:
- # For testing
- self.ts = numbered_symbols('t')
- # For various things that we change to make things work that we need to
- # change back when we are done.
- self.backsubs = []
- self.Tfuncs = []
- self.newf = self.f
- def indices(self, extension):
- """
- Parameters
- ==========
- extension : str
- Represents a valid extension type.
- Returns
- =======
- list: A list of indices of 'exts' where extension of
- type 'extension' is present.
- Examples
- ========
- >>> from sympy.integrals.risch import DifferentialExtension
- >>> from sympy import log, exp
- >>> from sympy.abc import x
- >>> DE = DifferentialExtension(log(x) + exp(x), x, handle_first='exp')
- >>> DE.indices('log')
- [2]
- >>> DE.indices('exp')
- [1]
- """
- return [i for i, ext in enumerate(self.exts) if ext == extension]
- def increment_level(self):
- """
- Increment the level of self.
- Explanation
- ===========
- This makes the working differential extension larger. self.level is
- given relative to the end of the list (-1, -2, etc.), so we do not need
- do worry about it when building the extension.
- """
- if self.level >= -1:
- raise ValueError("The level of the differential extension cannot "
- "be incremented any further.")
- self.level += 1
- self.t = self.T[self.level]
- self.d = self.D[self.level]
- self.case = self.cases[self.level]
- return None
- def decrement_level(self):
- """
- Decrease the level of self.
- Explanation
- ===========
- This makes the working differential extension smaller. self.level is
- given relative to the end of the list (-1, -2, etc.), so we do not need
- do worry about it when building the extension.
- """
- if self.level <= -len(self.T):
- raise ValueError("The level of the differential extension cannot "
- "be decremented any further.")
- self.level -= 1
- self.t = self.T[self.level]
- self.d = self.D[self.level]
- self.case = self.cases[self.level]
- return None
- def update_sets(seq, atoms, func):
- s = set(seq)
- s = atoms.intersection(s)
- new = atoms - s
- s.update(list(filter(func, new)))
- return list(s)
- class DecrementLevel:
- """
- A context manager for decrementing the level of a DifferentialExtension.
- """
- __slots__ = ('DE',)
- def __init__(self, DE):
- self.DE = DE
- return
- def __enter__(self):
- self.DE.decrement_level()
- def __exit__(self, exc_type, exc_value, traceback):
- self.DE.increment_level()
- class NonElementaryIntegralException(Exception):
- """
- Exception used by subroutines within the Risch algorithm to indicate to one
- another that the function being integrated does not have an elementary
- integral in the given differential field.
- """
- # TODO: Rewrite algorithms below to use this (?)
- # TODO: Pass through information about why the integral was nonelementary,
- # and store that in the resulting NonElementaryIntegral somehow.
- pass
- def gcdex_diophantine(a, b, c):
- """
- Extended Euclidean Algorithm, Diophantine version.
- Explanation
- ===========
- Given ``a``, ``b`` in K[x] and ``c`` in (a, b), the ideal generated by ``a`` and
- ``b``, return (s, t) such that s*a + t*b == c and either s == 0 or s.degree()
- < b.degree().
- """
- # Extended Euclidean Algorithm (Diophantine Version) pg. 13
- # TODO: This should go in densetools.py.
- # XXX: Bettter name?
- s, g = a.half_gcdex(b)
- s *= c.exquo(g) # Inexact division means c is not in (a, b)
- if s and s.degree() >= b.degree():
- _, s = s.div(b)
- t = (c - s*a).exquo(b)
- return (s, t)
- def frac_in(f, t, *, cancel=False, **kwargs):
- """
- Returns the tuple (fa, fd), where fa and fd are Polys in t.
- Explanation
- ===========
- This is a common idiom in the Risch Algorithm functions, so we abstract
- it out here. ``f`` should be a basic expression, a Poly, or a tuple (fa, fd),
- where fa and fd are either basic expressions or Polys, and f == fa/fd.
- **kwargs are applied to Poly.
- """
- if isinstance(f, tuple):
- fa, fd = f
- f = fa.as_expr()/fd.as_expr()
- fa, fd = f.as_expr().as_numer_denom()
- fa, fd = fa.as_poly(t, **kwargs), fd.as_poly(t, **kwargs)
- if cancel:
- fa, fd = fa.cancel(fd, include=True)
- if fa is None or fd is None:
- raise ValueError("Could not turn %s into a fraction in %s." % (f, t))
- return (fa, fd)
- def as_poly_1t(p, t, z):
- """
- (Hackish) way to convert an element ``p`` of K[t, 1/t] to K[t, z].
- In other words, ``z == 1/t`` will be a dummy variable that Poly can handle
- better.
- See issue 5131.
- Examples
- ========
- >>> from sympy import random_poly
- >>> from sympy.integrals.risch import as_poly_1t
- >>> from sympy.abc import x, z
- >>> p1 = random_poly(x, 10, -10, 10)
- >>> p2 = random_poly(x, 10, -10, 10)
- >>> p = p1 + p2.subs(x, 1/x)
- >>> as_poly_1t(p, x, z).as_expr().subs(z, 1/x) == p
- True
- """
- # TODO: Use this on the final result. That way, we can avoid answers like
- # (...)*exp(-x).
- pa, pd = frac_in(p, t, cancel=True)
- if not pd.is_monomial:
- # XXX: Is there a better Poly exception that we could raise here?
- # Either way, if you see this (from the Risch Algorithm) it indicates
- # a bug.
- raise PolynomialError("%s is not an element of K[%s, 1/%s]." % (p, t, t))
- d = pd.degree(t)
- one_t_part = pa.slice(0, d + 1)
- r = pd.degree() - pa.degree()
- t_part = pa - one_t_part
- try:
- t_part = t_part.to_field().exquo(pd)
- except DomainError as e:
- # issue 4950
- raise NotImplementedError(e)
- # Compute the negative degree parts.
- one_t_part = Poly.from_list(reversed(one_t_part.rep.rep), *one_t_part.gens,
- domain=one_t_part.domain)
- if 0 < r < oo:
- one_t_part *= Poly(t**r, t)
- one_t_part = one_t_part.replace(t, z) # z will be 1/t
- if pd.nth(d):
- one_t_part *= Poly(1/pd.nth(d), z, expand=False)
- ans = t_part.as_poly(t, z, expand=False) + one_t_part.as_poly(t, z,
- expand=False)
- return ans
- def derivation(p, DE, coefficientD=False, basic=False):
- """
- Computes Dp.
- Explanation
- ===========
- Given the derivation D with D = d/dx and p is a polynomial in t over
- K(x), return Dp.
- If coefficientD is True, it computes the derivation kD
- (kappaD), which is defined as kD(sum(ai*Xi**i, (i, 0, n))) ==
- sum(Dai*Xi**i, (i, 1, n)) (Definition 3.2.2, page 80). X in this case is
- T[-1], so coefficientD computes the derivative just with respect to T[:-1],
- with T[-1] treated as a constant.
- If ``basic=True``, the returns a Basic expression. Elements of D can still be
- instances of Poly.
- """
- if basic:
- r = 0
- else:
- r = Poly(0, DE.t)
- t = DE.t
- if coefficientD:
- if DE.level <= -len(DE.T):
- # 'base' case, the answer is 0.
- return r
- DE.decrement_level()
- D = DE.D[:len(DE.D) + DE.level + 1]
- T = DE.T[:len(DE.T) + DE.level + 1]
- for d, v in zip(D, T):
- pv = p.as_poly(v)
- if pv is None or basic:
- pv = p.as_expr()
- if basic:
- r += d.as_expr()*pv.diff(v)
- else:
- r += (d.as_expr()*pv.diff(v).as_expr()).as_poly(t)
- if basic:
- r = cancel(r)
- if coefficientD:
- DE.increment_level()
- return r
- def get_case(d, t):
- """
- Returns the type of the derivation d.
- Returns one of {'exp', 'tan', 'base', 'primitive', 'other_linear',
- 'other_nonlinear'}.
- """
- if not d.expr.has(t):
- if d.is_one:
- return 'base'
- return 'primitive'
- if d.rem(Poly(t, t)).is_zero:
- return 'exp'
- if d.rem(Poly(1 + t**2, t)).is_zero:
- return 'tan'
- if d.degree(t) > 1:
- return 'other_nonlinear'
- return 'other_linear'
- def splitfactor(p, DE, coefficientD=False, z=None):
- """
- Splitting factorization.
- Explanation
- ===========
- Given a derivation D on k[t] and ``p`` in k[t], return (p_n, p_s) in
- k[t] x k[t] such that p = p_n*p_s, p_s is special, and each square
- factor of p_n is normal.
- Page. 100
- """
- kinv = [1/x for x in DE.T[:DE.level]]
- if z:
- kinv.append(z)
- One = Poly(1, DE.t, domain=p.get_domain())
- Dp = derivation(p, DE, coefficientD=coefficientD)
- # XXX: Is this right?
- if p.is_zero:
- return (p, One)
- if not p.expr.has(DE.t):
- s = p.as_poly(*kinv).gcd(Dp.as_poly(*kinv)).as_poly(DE.t)
- n = p.exquo(s)
- return (n, s)
- if not Dp.is_zero:
- h = p.gcd(Dp).to_field()
- g = p.gcd(p.diff(DE.t)).to_field()
- s = h.exquo(g)
- if s.degree(DE.t) == 0:
- return (p, One)
- q_split = splitfactor(p.exquo(s), DE, coefficientD=coefficientD)
- return (q_split[0], q_split[1]*s)
- else:
- return (p, One)
- def splitfactor_sqf(p, DE, coefficientD=False, z=None, basic=False):
- """
- Splitting Square-free Factorization.
- Explanation
- ===========
- Given a derivation D on k[t] and ``p`` in k[t], returns (N1, ..., Nm)
- and (S1, ..., Sm) in k[t]^m such that p =
- (N1*N2**2*...*Nm**m)*(S1*S2**2*...*Sm**m) is a splitting
- factorization of ``p`` and the Ni and Si are square-free and coprime.
- """
- # TODO: This algorithm appears to be faster in every case
- # TODO: Verify this and splitfactor() for multiple extensions
- kkinv = [1/x for x in DE.T[:DE.level]] + DE.T[:DE.level]
- if z:
- kkinv = [z]
- S = []
- N = []
- p_sqf = p.sqf_list_include()
- if p.is_zero:
- return (((p, 1),), ())
- for pi, i in p_sqf:
- Si = pi.as_poly(*kkinv).gcd(derivation(pi, DE,
- coefficientD=coefficientD,basic=basic).as_poly(*kkinv)).as_poly(DE.t)
- pi = Poly(pi, DE.t)
- Si = Poly(Si, DE.t)
- Ni = pi.exquo(Si)
- if not Si.is_one:
- S.append((Si, i))
- if not Ni.is_one:
- N.append((Ni, i))
- return (tuple(N), tuple(S))
- def canonical_representation(a, d, DE):
- """
- Canonical Representation.
- Explanation
- ===========
- Given a derivation D on k[t] and f = a/d in k(t), return (f_p, f_s,
- f_n) in k[t] x k(t) x k(t) such that f = f_p + f_s + f_n is the
- canonical representation of f (f_p is a polynomial, f_s is reduced
- (has a special denominator), and f_n is simple (has a normal
- denominator).
- """
- # Make d monic
- l = Poly(1/d.LC(), DE.t)
- a, d = a.mul(l), d.mul(l)
- q, r = a.div(d)
- dn, ds = splitfactor(d, DE)
- b, c = gcdex_diophantine(dn.as_poly(DE.t), ds.as_poly(DE.t), r.as_poly(DE.t))
- b, c = b.as_poly(DE.t), c.as_poly(DE.t)
- return (q, (b, ds), (c, dn))
- def hermite_reduce(a, d, DE):
- """
- Hermite Reduction - Mack's Linear Version.
- Given a derivation D on k(t) and f = a/d in k(t), returns g, h, r in
- k(t) such that f = Dg + h + r, h is simple, and r is reduced.
- """
- # Make d monic
- l = Poly(1/d.LC(), DE.t)
- a, d = a.mul(l), d.mul(l)
- fp, fs, fn = canonical_representation(a, d, DE)
- a, d = fn
- l = Poly(1/d.LC(), DE.t)
- a, d = a.mul(l), d.mul(l)
- ga = Poly(0, DE.t)
- gd = Poly(1, DE.t)
- dd = derivation(d, DE)
- dm = gcd(d.to_field(), dd.to_field()).as_poly(DE.t)
- ds, _ = d.div(dm)
- while dm.degree(DE.t) > 0:
- ddm = derivation(dm, DE)
- dm2 = gcd(dm.to_field(), ddm.to_field())
- dms, _ = dm.div(dm2)
- ds_ddm = ds.mul(ddm)
- ds_ddm_dm, _ = ds_ddm.div(dm)
- b, c = gcdex_diophantine(-ds_ddm_dm.as_poly(DE.t),
- dms.as_poly(DE.t), a.as_poly(DE.t))
- b, c = b.as_poly(DE.t), c.as_poly(DE.t)
- db = derivation(b, DE).as_poly(DE.t)
- ds_dms, _ = ds.div(dms)
- a = c.as_poly(DE.t) - db.mul(ds_dms).as_poly(DE.t)
- ga = ga*dm + b*gd
- gd = gd*dm
- ga, gd = ga.cancel(gd, include=True)
- dm = dm2
- q, r = a.div(ds)
- ga, gd = ga.cancel(gd, include=True)
- r, d = r.cancel(ds, include=True)
- rra = q*fs[1] + fp*fs[1] + fs[0]
- rrd = fs[1]
- rra, rrd = rra.cancel(rrd, include=True)
- return ((ga, gd), (r, d), (rra, rrd))
- def polynomial_reduce(p, DE):
- """
- Polynomial Reduction.
- Explanation
- ===========
- Given a derivation D on k(t) and p in k[t] where t is a nonlinear
- monomial over k, return q, r in k[t] such that p = Dq + r, and
- deg(r) < deg_t(Dt).
- """
- q = Poly(0, DE.t)
- while p.degree(DE.t) >= DE.d.degree(DE.t):
- m = p.degree(DE.t) - DE.d.degree(DE.t) + 1
- q0 = Poly(DE.t**m, DE.t).mul(Poly(p.as_poly(DE.t).LC()/
- (m*DE.d.LC()), DE.t))
- q += q0
- p = p - derivation(q0, DE)
- return (q, p)
- def laurent_series(a, d, F, n, DE):
- """
- Contribution of ``F`` to the full partial fraction decomposition of A/D.
- Explanation
- ===========
- Given a field K of characteristic 0 and ``A``,``D``,``F`` in K[x] with D monic,
- nonzero, coprime with A, and ``F`` the factor of multiplicity n in the square-
- free factorization of D, return the principal parts of the Laurent series of
- A/D at all the zeros of ``F``.
- """
- if F.degree()==0:
- return 0
- Z = _symbols('z', n)
- z = Symbol('z')
- Z.insert(0, z)
- delta_a = Poly(0, DE.t)
- delta_d = Poly(1, DE.t)
- E = d.quo(F**n)
- ha, hd = (a, E*Poly(z**n, DE.t))
- dF = derivation(F,DE)
- B, _ = gcdex_diophantine(E, F, Poly(1,DE.t))
- C, _ = gcdex_diophantine(dF, F, Poly(1,DE.t))
- # initialization
- F_store = F
- V, DE_D_list, H_list= [], [], []
- for j in range(0, n):
- # jth derivative of z would be substituted with dfnth/(j+1) where dfnth =(d^n)f/(dx)^n
- F_store = derivation(F_store, DE)
- v = (F_store.as_expr())/(j + 1)
- V.append(v)
- DE_D_list.append(Poly(Z[j + 1],Z[j]))
- DE_new = DifferentialExtension(extension = {'D': DE_D_list}) #a differential indeterminate
- for j in range(0, n):
- zEha = Poly(z**(n + j), DE.t)*E**(j + 1)*ha
- zEhd = hd
- Pa, Pd = cancel((zEha, zEhd))[1], cancel((zEha, zEhd))[2]
- Q = Pa.quo(Pd)
- for i in range(0, j + 1):
- Q = Q.subs(Z[i], V[i])
- Dha = (hd*derivation(ha, DE, basic=True).as_poly(DE.t)
- + ha*derivation(hd, DE, basic=True).as_poly(DE.t)
- + hd*derivation(ha, DE_new, basic=True).as_poly(DE.t)
- + ha*derivation(hd, DE_new, basic=True).as_poly(DE.t))
- Dhd = Poly(j + 1, DE.t)*hd**2
- ha, hd = Dha, Dhd
- Ff, _ = F.div(gcd(F, Q))
- F_stara, F_stard = frac_in(Ff, DE.t)
- if F_stara.degree(DE.t) - F_stard.degree(DE.t) > 0:
- QBC = Poly(Q, DE.t)*B**(1 + j)*C**(n + j)
- H = QBC
- H_list.append(H)
- H = (QBC*F_stard).rem(F_stara)
- alphas = real_roots(F_stara)
- for alpha in list(alphas):
- delta_a = delta_a*Poly((DE.t - alpha)**(n - j), DE.t) + Poly(H.eval(alpha), DE.t)
- delta_d = delta_d*Poly((DE.t - alpha)**(n - j), DE.t)
- return (delta_a, delta_d, H_list)
- def recognize_derivative(a, d, DE, z=None):
- """
- Compute the squarefree factorization of the denominator of f
- and for each Di the polynomial H in K[x] (see Theorem 2.7.1), using the
- LaurentSeries algorithm. Write Di = GiEi where Gj = gcd(Hn, Di) and
- gcd(Ei,Hn) = 1. Since the residues of f at the roots of Gj are all 0, and
- the residue of f at a root alpha of Ei is Hi(a) != 0, f is the derivative of a
- rational function if and only if Ei = 1 for each i, which is equivalent to
- Di | H[-1] for each i.
- """
- flag =True
- a, d = a.cancel(d, include=True)
- _, r = a.div(d)
- Np, Sp = splitfactor_sqf(d, DE, coefficientD=True, z=z)
- j = 1
- for s, _ in Sp:
- delta_a, delta_d, H = laurent_series(r, d, s, j, DE)
- g = gcd(d, H[-1]).as_poly()
- if g is not d:
- flag = False
- break
- j = j + 1
- return flag
- def recognize_log_derivative(a, d, DE, z=None):
- """
- There exists a v in K(x)* such that f = dv/v
- where f a rational function if and only if f can be written as f = A/D
- where D is squarefree,deg(A) < deg(D), gcd(A, D) = 1,
- and all the roots of the Rothstein-Trager resultant are integers. In that case,
- any of the Rothstein-Trager, Lazard-Rioboo-Trager or Czichowski algorithm
- produces u in K(x) such that du/dx = uf.
- """
- z = z or Dummy('z')
- a, d = a.cancel(d, include=True)
- _, a = a.div(d)
- pz = Poly(z, DE.t)
- Dd = derivation(d, DE)
- q = a - pz*Dd
- r, _ = d.resultant(q, includePRS=True)
- r = Poly(r, z)
- Np, Sp = splitfactor_sqf(r, DE, coefficientD=True, z=z)
- for s, _ in Sp:
- # TODO also consider the complex roots
- # incase we have complex roots it should turn the flag false
- a = real_roots(s.as_poly(z))
- if not all(j.is_Integer for j in a):
- return False
- return True
- def residue_reduce(a, d, DE, z=None, invert=True):
- """
- Lazard-Rioboo-Rothstein-Trager resultant reduction.
- Explanation
- ===========
- Given a derivation ``D`` on k(t) and f in k(t) simple, return g
- elementary over k(t) and a Boolean b in {True, False} such that f -
- Dg in k[t] if b == True or f + h and f + h - Dg do not have an
- elementary integral over k(t) for any h in k<t> (reduced) if b ==
- False.
- Returns (G, b), where G is a tuple of tuples of the form (s_i, S_i),
- such that g = Add(*[RootSum(s_i, lambda z: z*log(S_i(z, t))) for
- S_i, s_i in G]). f - Dg is the remaining integral, which is elementary
- only if b == True, and hence the integral of f is elementary only if
- b == True.
- f - Dg is not calculated in this function because that would require
- explicitly calculating the RootSum. Use residue_reduce_derivation().
- """
- # TODO: Use log_to_atan() from rationaltools.py
- # If r = residue_reduce(...), then the logarithmic part is given by:
- # sum([RootSum(a[0].as_poly(z), lambda i: i*log(a[1].as_expr()).subs(z,
- # i)).subs(t, log(x)) for a in r[0]])
- z = z or Dummy('z')
- a, d = a.cancel(d, include=True)
- a, d = a.to_field().mul_ground(1/d.LC()), d.to_field().mul_ground(1/d.LC())
- kkinv = [1/x for x in DE.T[:DE.level]] + DE.T[:DE.level]
- if a.is_zero:
- return ([], True)
- _, a = a.div(d)
- pz = Poly(z, DE.t)
- Dd = derivation(d, DE)
- q = a - pz*Dd
- if Dd.degree(DE.t) <= d.degree(DE.t):
- r, R = d.resultant(q, includePRS=True)
- else:
- r, R = q.resultant(d, includePRS=True)
- R_map, H = {}, []
- for i in R:
- R_map[i.degree()] = i
- r = Poly(r, z)
- Np, Sp = splitfactor_sqf(r, DE, coefficientD=True, z=z)
- for s, i in Sp:
- if i == d.degree(DE.t):
- s = Poly(s, z).monic()
- H.append((s, d))
- else:
- h = R_map.get(i)
- if h is None:
- continue
- h_lc = Poly(h.as_poly(DE.t).LC(), DE.t, field=True)
- h_lc_sqf = h_lc.sqf_list_include(all=True)
- for a, j in h_lc_sqf:
- h = Poly(h, DE.t, field=True).exquo(Poly(gcd(a, s**j, *kkinv),
- DE.t))
- s = Poly(s, z).monic()
- if invert:
- h_lc = Poly(h.as_poly(DE.t).LC(), DE.t, field=True, expand=False)
- inv, coeffs = h_lc.as_poly(z, field=True).invert(s), [S.One]
- for coeff in h.coeffs()[1:]:
- L = reduced(inv*coeff.as_poly(inv.gens), [s])[1]
- coeffs.append(L.as_expr())
- h = Poly(dict(list(zip(h.monoms(), coeffs))), DE.t)
- H.append((s, h))
- b = not any(cancel(i.as_expr()).has(DE.t, z) for i, _ in Np)
- return (H, b)
- def residue_reduce_to_basic(H, DE, z):
- """
- Converts the tuple returned by residue_reduce() into a Basic expression.
- """
- # TODO: check what Lambda does with RootOf
- i = Dummy('i')
- s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs])))
- return sum(RootSum(a[0].as_poly(z), Lambda(i, i*log(a[1].as_expr()).subs(
- {z: i}).subs(s))) for a in H)
- def residue_reduce_derivation(H, DE, z):
- """
- Computes the derivation of an expression returned by residue_reduce().
- In general, this is a rational function in t, so this returns an
- as_expr() result.
- """
- # TODO: verify that this is correct for multiple extensions
- i = Dummy('i')
- return S(sum(RootSum(a[0].as_poly(z), Lambda(i, i*derivation(a[1],
- DE).as_expr().subs(z, i)/a[1].as_expr().subs(z, i))) for a in H))
- def integrate_primitive_polynomial(p, DE):
- """
- Integration of primitive polynomials.
- Explanation
- ===========
- Given a primitive monomial t over k, and ``p`` in k[t], return q in k[t],
- r in k, and a bool b in {True, False} such that r = p - Dq is in k if b is
- True, or r = p - Dq does not have an elementary integral over k(t) if b is
- False.
- """
- Zero = Poly(0, DE.t)
- q = Poly(0, DE.t)
- if not p.expr.has(DE.t):
- return (Zero, p, True)
- from .prde import limited_integrate
- while True:
- if not p.expr.has(DE.t):
- return (q, p, True)
- Dta, Dtb = frac_in(DE.d, DE.T[DE.level - 1])
- with DecrementLevel(DE): # We had better be integrating the lowest extension (x)
- # with ratint().
- a = p.LC()
- aa, ad = frac_in(a, DE.t)
- try:
- rv = limited_integrate(aa, ad, [(Dta, Dtb)], DE)
- if rv is None:
- raise NonElementaryIntegralException
- (ba, bd), c = rv
- except NonElementaryIntegralException:
- return (q, p, False)
- m = p.degree(DE.t)
- q0 = c[0].as_poly(DE.t)*Poly(DE.t**(m + 1)/(m + 1), DE.t) + \
- (ba.as_expr()/bd.as_expr()).as_poly(DE.t)*Poly(DE.t**m, DE.t)
- p = p - derivation(q0, DE)
- q = q + q0
- def integrate_primitive(a, d, DE, z=None):
- """
- Integration of primitive functions.
- Explanation
- ===========
- Given a primitive monomial t over k and f in k(t), return g elementary over
- k(t), i in k(t), and b in {True, False} such that i = f - Dg is in k if b
- is True or i = f - Dg does not have an elementary integral over k(t) if b
- is False.
- This function returns a Basic expression for the first argument. If b is
- True, the second argument is Basic expression in k to recursively integrate.
- If b is False, the second argument is an unevaluated Integral, which has
- been proven to be nonelementary.
- """
- # XXX: a and d must be canceled, or this might return incorrect results
- z = z or Dummy("z")
- s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs])))
- g1, h, r = hermite_reduce(a, d, DE)
- g2, b = residue_reduce(h[0], h[1], DE, z=z)
- if not b:
- i = cancel(a.as_expr()/d.as_expr() - (g1[1]*derivation(g1[0], DE) -
- g1[0]*derivation(g1[1], DE)).as_expr()/(g1[1]**2).as_expr() -
- residue_reduce_derivation(g2, DE, z))
- i = NonElementaryIntegral(cancel(i).subs(s), DE.x)
- return ((g1[0].as_expr()/g1[1].as_expr()).subs(s) +
- residue_reduce_to_basic(g2, DE, z), i, b)
- # h - Dg2 + r
- p = cancel(h[0].as_expr()/h[1].as_expr() - residue_reduce_derivation(g2,
- DE, z) + r[0].as_expr()/r[1].as_expr())
- p = p.as_poly(DE.t)
- q, i, b = integrate_primitive_polynomial(p, DE)
- ret = ((g1[0].as_expr()/g1[1].as_expr() + q.as_expr()).subs(s) +
- residue_reduce_to_basic(g2, DE, z))
- if not b:
- # TODO: This does not do the right thing when b is False
- i = NonElementaryIntegral(cancel(i.as_expr()).subs(s), DE.x)
- else:
- i = cancel(i.as_expr())
- return (ret, i, b)
- def integrate_hyperexponential_polynomial(p, DE, z):
- """
- Integration of hyperexponential polynomials.
- Explanation
- ===========
- Given a hyperexponential monomial t over k and ``p`` in k[t, 1/t], return q in
- k[t, 1/t] and a bool b in {True, False} such that p - Dq in k if b is True,
- or p - Dq does not have an elementary integral over k(t) if b is False.
- """
- t1 = DE.t
- dtt = DE.d.exquo(Poly(DE.t, DE.t))
- qa = Poly(0, DE.t)
- qd = Poly(1, DE.t)
- b = True
- if p.is_zero:
- return(qa, qd, b)
- from sympy.integrals.rde import rischDE
- with DecrementLevel(DE):
- for i in range(-p.degree(z), p.degree(t1) + 1):
- if not i:
- continue
- elif i < 0:
- # If you get AttributeError: 'NoneType' object has no attribute 'nth'
- # then this should really not have expand=False
- # But it shouldn't happen because p is already a Poly in t and z
- a = p.as_poly(z, expand=False).nth(-i)
- else:
- # If you get AttributeError: 'NoneType' object has no attribute 'nth'
- # then this should really not have expand=False
- a = p.as_poly(t1, expand=False).nth(i)
- aa, ad = frac_in(a, DE.t, field=True)
- aa, ad = aa.cancel(ad, include=True)
- iDt = Poly(i, t1)*dtt
- iDta, iDtd = frac_in(iDt, DE.t, field=True)
- try:
- va, vd = rischDE(iDta, iDtd, Poly(aa, DE.t), Poly(ad, DE.t), DE)
- va, vd = frac_in((va, vd), t1, cancel=True)
- except NonElementaryIntegralException:
- b = False
- else:
- qa = qa*vd + va*Poly(t1**i)*qd
- qd *= vd
- return (qa, qd, b)
- def integrate_hyperexponential(a, d, DE, z=None, conds='piecewise'):
- """
- Integration of hyperexponential functions.
- Explanation
- ===========
- Given a hyperexponential monomial t over k and f in k(t), return g
- elementary over k(t), i in k(t), and a bool b in {True, False} such that
- i = f - Dg is in k if b is True or i = f - Dg does not have an elementary
- integral over k(t) if b is False.
- This function returns a Basic expression for the first argument. If b is
- True, the second argument is Basic expression in k to recursively integrate.
- If b is False, the second argument is an unevaluated Integral, which has
- been proven to be nonelementary.
- """
- # XXX: a and d must be canceled, or this might return incorrect results
- z = z or Dummy("z")
- s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs])))
- g1, h, r = hermite_reduce(a, d, DE)
- g2, b = residue_reduce(h[0], h[1], DE, z=z)
- if not b:
- i = cancel(a.as_expr()/d.as_expr() - (g1[1]*derivation(g1[0], DE) -
- g1[0]*derivation(g1[1], DE)).as_expr()/(g1[1]**2).as_expr() -
- residue_reduce_derivation(g2, DE, z))
- i = NonElementaryIntegral(cancel(i.subs(s)), DE.x)
- return ((g1[0].as_expr()/g1[1].as_expr()).subs(s) +
- residue_reduce_to_basic(g2, DE, z), i, b)
- # p should be a polynomial in t and 1/t, because Sirr == k[t, 1/t]
- # h - Dg2 + r
- p = cancel(h[0].as_expr()/h[1].as_expr() - residue_reduce_derivation(g2,
- DE, z) + r[0].as_expr()/r[1].as_expr())
- pp = as_poly_1t(p, DE.t, z)
- qa, qd, b = integrate_hyperexponential_polynomial(pp, DE, z)
- i = pp.nth(0, 0)
- ret = ((g1[0].as_expr()/g1[1].as_expr()).subs(s) \
- + residue_reduce_to_basic(g2, DE, z))
- qas = qa.as_expr().subs(s)
- qds = qd.as_expr().subs(s)
- if conds == 'piecewise' and DE.x not in qds.free_symbols:
- # We have to be careful if the exponent is S.Zero!
- # XXX: Does qd = 0 always necessarily correspond to the exponential
- # equaling 1?
- ret += Piecewise(
- (qas/qds, Ne(qds, 0)),
- (integrate((p - i).subs(DE.t, 1).subs(s), DE.x), True)
- )
- else:
- ret += qas/qds
- if not b:
- i = p - (qd*derivation(qa, DE) - qa*derivation(qd, DE)).as_expr()/\
- (qd**2).as_expr()
- i = NonElementaryIntegral(cancel(i).subs(s), DE.x)
- return (ret, i, b)
- def integrate_hypertangent_polynomial(p, DE):
- """
- Integration of hypertangent polynomials.
- Explanation
- ===========
- Given a differential field k such that sqrt(-1) is not in k, a
- hypertangent monomial t over k, and p in k[t], return q in k[t] and
- c in k such that p - Dq - c*D(t**2 + 1)/(t**1 + 1) is in k and p -
- Dq does not have an elementary integral over k(t) if Dc != 0.
- """
- # XXX: Make sure that sqrt(-1) is not in k.
- q, r = polynomial_reduce(p, DE)
- a = DE.d.exquo(Poly(DE.t**2 + 1, DE.t))
- c = Poly(r.nth(1)/(2*a.as_expr()), DE.t)
- return (q, c)
- def integrate_nonlinear_no_specials(a, d, DE, z=None):
- """
- Integration of nonlinear monomials with no specials.
- Explanation
- ===========
- Given a nonlinear monomial t over k such that Sirr ({p in k[t] | p is
- special, monic, and irreducible}) is empty, and f in k(t), returns g
- elementary over k(t) and a Boolean b in {True, False} such that f - Dg is
- in k if b == True, or f - Dg does not have an elementary integral over k(t)
- if b == False.
- This function is applicable to all nonlinear extensions, but in the case
- where it returns b == False, it will only have proven that the integral of
- f - Dg is nonelementary if Sirr is empty.
- This function returns a Basic expression.
- """
- # TODO: Integral from k?
- # TODO: split out nonelementary integral
- # XXX: a and d must be canceled, or this might not return correct results
- z = z or Dummy("z")
- s = list(zip(reversed(DE.T), reversed([f(DE.x) for f in DE.Tfuncs])))
- g1, h, r = hermite_reduce(a, d, DE)
- g2, b = residue_reduce(h[0], h[1], DE, z=z)
- if not b:
- return ((g1[0].as_expr()/g1[1].as_expr()).subs(s) +
- residue_reduce_to_basic(g2, DE, z), b)
- # Because f has no specials, this should be a polynomial in t, or else
- # there is a bug.
- p = cancel(h[0].as_expr()/h[1].as_expr() - residue_reduce_derivation(g2,
- DE, z).as_expr() + r[0].as_expr()/r[1].as_expr()).as_poly(DE.t)
- q1, q2 = polynomial_reduce(p, DE)
- if q2.expr.has(DE.t):
- b = False
- else:
- b = True
- ret = (cancel(g1[0].as_expr()/g1[1].as_expr() + q1.as_expr()).subs(s) +
- residue_reduce_to_basic(g2, DE, z))
- return (ret, b)
- class NonElementaryIntegral(Integral):
- """
- Represents a nonelementary Integral.
- Explanation
- ===========
- If the result of integrate() is an instance of this class, it is
- guaranteed to be nonelementary. Note that integrate() by default will try
- to find any closed-form solution, even in terms of special functions which
- may themselves not be elementary. To make integrate() only give
- elementary solutions, or, in the cases where it can prove the integral to
- be nonelementary, instances of this class, use integrate(risch=True).
- In this case, integrate() may raise NotImplementedError if it cannot make
- such a determination.
- integrate() uses the deterministic Risch algorithm to integrate elementary
- functions or prove that they have no elementary integral. In some cases,
- this algorithm can split an integral into an elementary and nonelementary
- part, so that the result of integrate will be the sum of an elementary
- expression and a NonElementaryIntegral.
- Examples
- ========
- >>> from sympy import integrate, exp, log, Integral
- >>> from sympy.abc import x
- >>> a = integrate(exp(-x**2), x, risch=True)
- >>> print(a)
- Integral(exp(-x**2), x)
- >>> type(a)
- <class 'sympy.integrals.risch.NonElementaryIntegral'>
- >>> expr = (2*log(x)**2 - log(x) - x**2)/(log(x)**3 - x**2*log(x))
- >>> b = integrate(expr, x, risch=True)
- >>> print(b)
- -log(-x + log(x))/2 + log(x + log(x))/2 + Integral(1/log(x), x)
- >>> type(b.atoms(Integral).pop())
- <class 'sympy.integrals.risch.NonElementaryIntegral'>
- """
- # TODO: This is useful in and of itself, because isinstance(result,
- # NonElementaryIntegral) will tell if the integral has been proven to be
- # elementary. But should we do more? Perhaps a no-op .doit() if
- # elementary=True? Or maybe some information on why the integral is
- # nonelementary.
- pass
- def risch_integrate(f, x, extension=None, handle_first='log',
- separate_integral=False, rewrite_complex=None,
- conds='piecewise'):
- r"""
- The Risch Integration Algorithm.
- Explanation
- ===========
- Only transcendental functions are supported. Currently, only exponentials
- and logarithms are supported, but support for trigonometric functions is
- forthcoming.
- If this function returns an unevaluated Integral in the result, it means
- that it has proven that integral to be nonelementary. Any errors will
- result in raising NotImplementedError. The unevaluated Integral will be
- an instance of NonElementaryIntegral, a subclass of Integral.
- handle_first may be either 'exp' or 'log'. This changes the order in
- which the extension is built, and may result in a different (but
- equivalent) solution (for an example of this, see issue 5109). It is also
- possible that the integral may be computed with one but not the other,
- because not all cases have been implemented yet. It defaults to 'log' so
- that the outer extension is exponential when possible, because more of the
- exponential case has been implemented.
- If ``separate_integral`` is ``True``, the result is returned as a tuple (ans, i),
- where the integral is ans + i, ans is elementary, and i is either a
- NonElementaryIntegral or 0. This useful if you want to try further
- integrating the NonElementaryIntegral part using other algorithms to
- possibly get a solution in terms of special functions. It is False by
- default.
- Examples
- ========
- >>> from sympy.integrals.risch import risch_integrate
- >>> from sympy import exp, log, pprint
- >>> from sympy.abc import x
- First, we try integrating exp(-x**2). Except for a constant factor of
- 2/sqrt(pi), this is the famous error function.
- >>> pprint(risch_integrate(exp(-x**2), x))
- /
- |
- | 2
- | -x
- | e dx
- |
- /
- The unevaluated Integral in the result means that risch_integrate() has
- proven that exp(-x**2) does not have an elementary anti-derivative.
- In many cases, risch_integrate() can split out the elementary
- anti-derivative part from the nonelementary anti-derivative part.
- For example,
- >>> pprint(risch_integrate((2*log(x)**2 - log(x) - x**2)/(log(x)**3 -
- ... x**2*log(x)), x))
- /
- |
- log(-x + log(x)) log(x + log(x)) | 1
- - ---------------- + --------------- + | ------ dx
- 2 2 | log(x)
- |
- /
- This means that it has proven that the integral of 1/log(x) is
- nonelementary. This function is also known as the logarithmic integral,
- and is often denoted as Li(x).
- risch_integrate() currently only accepts purely transcendental functions
- with exponentials and logarithms, though note that this can include
- nested exponentials and logarithms, as well as exponentials with bases
- other than E.
- >>> pprint(risch_integrate(exp(x)*exp(exp(x)), x))
- / x\
- \e /
- e
- >>> pprint(risch_integrate(exp(exp(x)), x))
- /
- |
- | / x\
- | \e /
- | e dx
- |
- /
- >>> pprint(risch_integrate(x*x**x*log(x) + x**x + x*x**x, x))
- x
- x*x
- >>> pprint(risch_integrate(x**x, x))
- /
- |
- | x
- | x dx
- |
- /
- >>> pprint(risch_integrate(-1/(x*log(x)*log(log(x))**2), x))
- 1
- -----------
- log(log(x))
- """
- f = S(f)
- DE = extension or DifferentialExtension(f, x, handle_first=handle_first,
- dummy=True, rewrite_complex=rewrite_complex)
- fa, fd = DE.fa, DE.fd
- result = S.Zero
- for case in reversed(DE.cases):
- if not fa.expr.has(DE.t) and not fd.expr.has(DE.t) and not case == 'base':
- DE.decrement_level()
- fa, fd = frac_in((fa, fd), DE.t)
- continue
- fa, fd = fa.cancel(fd, include=True)
- if case == 'exp':
- ans, i, b = integrate_hyperexponential(fa, fd, DE, conds=conds)
- elif case == 'primitive':
- ans, i, b = integrate_primitive(fa, fd, DE)
- elif case == 'base':
- # XXX: We can't call ratint() directly here because it doesn't
- # handle polynomials correctly.
- ans = integrate(fa.as_expr()/fd.as_expr(), DE.x, risch=False)
- b = False
- i = S.Zero
- else:
- raise NotImplementedError("Only exponential and logarithmic "
- "extensions are currently supported.")
- result += ans
- if b:
- DE.decrement_level()
- fa, fd = frac_in(i, DE.t)
- else:
- result = result.subs(DE.backsubs)
- if not i.is_zero:
- i = NonElementaryIntegral(i.function.subs(DE.backsubs),i.limits)
- if not separate_integral:
- result += i
- return result
- else:
- if isinstance(i, NonElementaryIntegral):
- return (result, i)
- else:
- return (result, 0)
|