|
from __future__ import annotations |
|
|
|
from collections import defaultdict |
|
from functools import reduce |
|
from itertools import permutations |
|
|
|
from sympy.core.add import Add |
|
from sympy.core.basic import Basic |
|
from sympy.core.mul import Mul |
|
from sympy.core.symbol import Wild, Dummy, Symbol |
|
from sympy.core.basic import sympify |
|
from sympy.core.numbers import Rational, pi, I |
|
from sympy.core.relational import Eq, Ne |
|
from sympy.core.singleton import S |
|
from sympy.core.sorting import ordered |
|
from sympy.core.traversal import iterfreeargs |
|
|
|
from sympy.functions import exp, sin, cos, tan, cot, asin, atan |
|
from sympy.functions import log, sinh, cosh, tanh, coth, asinh |
|
from sympy.functions import sqrt, erf, erfi, li, Ei |
|
from sympy.functions import besselj, bessely, besseli, besselk |
|
from sympy.functions import hankel1, hankel2, jn, yn |
|
from sympy.functions.elementary.complexes import Abs, re, im, sign, arg |
|
from sympy.functions.elementary.exponential import LambertW |
|
from sympy.functions.elementary.integers import floor, ceiling |
|
from sympy.functions.elementary.piecewise import Piecewise |
|
from sympy.functions.special.delta_functions import Heaviside, DiracDelta |
|
|
|
from sympy.simplify.radsimp import collect |
|
|
|
from sympy.logic.boolalg import And, Or |
|
from sympy.utilities.iterables import uniq |
|
|
|
from sympy.polys import quo, gcd, lcm, factor_list, cancel, PolynomialError |
|
from sympy.polys.monomials import itermonomials |
|
from sympy.polys.polyroots import root_factors |
|
|
|
from sympy.polys.rings import PolyRing |
|
from sympy.polys.solvers import solve_lin_sys |
|
from sympy.polys.constructor import construct_domain |
|
|
|
from sympy.integrals.integrals import integrate |
|
|
|
|
|
def components(f, x): |
|
""" |
|
Returns a set of all functional components of the given expression |
|
which includes symbols, function applications and compositions and |
|
non-integer powers. Fractional powers are collected with |
|
minimal, positive exponents. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import cos, sin |
|
>>> from sympy.abc import x |
|
>>> from sympy.integrals.heurisch import components |
|
|
|
>>> components(sin(x)*cos(x)**2, x) |
|
{x, sin(x), cos(x)} |
|
|
|
See Also |
|
======== |
|
|
|
heurisch |
|
""" |
|
result = set() |
|
|
|
if f.has_free(x): |
|
if f.is_symbol and f.is_commutative: |
|
result.add(f) |
|
elif f.is_Function or f.is_Derivative: |
|
for g in f.args: |
|
result |= components(g, x) |
|
|
|
result.add(f) |
|
elif f.is_Pow: |
|
result |= components(f.base, x) |
|
|
|
if not f.exp.is_Integer: |
|
if f.exp.is_Rational: |
|
result.add(f.base**Rational(1, f.exp.q)) |
|
else: |
|
result |= components(f.exp, x) | {f} |
|
else: |
|
for g in f.args: |
|
result |= components(g, x) |
|
|
|
return result |
|
|
|
|
|
_symbols_cache: dict[str, list[Dummy]] = {} |
|
|
|
|
|
|
|
def _symbols(name, n): |
|
"""get vector of symbols local to this module""" |
|
try: |
|
lsyms = _symbols_cache[name] |
|
except KeyError: |
|
lsyms = [] |
|
_symbols_cache[name] = lsyms |
|
|
|
while len(lsyms) < n: |
|
lsyms.append( Dummy('%s%i' % (name, len(lsyms))) ) |
|
|
|
return lsyms[:n] |
|
|
|
|
|
def heurisch_wrapper(f, x, rewrite=False, hints=None, mappings=None, retries=3, |
|
degree_offset=0, unnecessary_permutations=None, |
|
_try_heurisch=None): |
|
""" |
|
A wrapper around the heurisch integration algorithm. |
|
|
|
Explanation |
|
=========== |
|
|
|
This method takes the result from heurisch and checks for poles in the |
|
denominator. For each of these poles, the integral is reevaluated, and |
|
the final integration result is given in terms of a Piecewise. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import cos, symbols |
|
>>> from sympy.integrals.heurisch import heurisch, heurisch_wrapper |
|
>>> n, x = symbols('n x') |
|
>>> heurisch(cos(n*x), x) |
|
sin(n*x)/n |
|
>>> heurisch_wrapper(cos(n*x), x) |
|
Piecewise((sin(n*x)/n, Ne(n, 0)), (x, True)) |
|
|
|
See Also |
|
======== |
|
|
|
heurisch |
|
""" |
|
from sympy.solvers.solvers import solve, denoms |
|
f = sympify(f) |
|
if not f.has_free(x): |
|
return f*x |
|
|
|
res = heurisch(f, x, rewrite, hints, mappings, retries, degree_offset, |
|
unnecessary_permutations, _try_heurisch) |
|
if not isinstance(res, Basic): |
|
return res |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
slns = [] |
|
for d in ordered(denoms(res)): |
|
try: |
|
slns += solve([d], dict=True, exclude=(x,)) |
|
except NotImplementedError: |
|
pass |
|
if not slns: |
|
return res |
|
slns = list(uniq(slns)) |
|
|
|
slns0 = [] |
|
for d in denoms(f): |
|
try: |
|
slns0 += solve([d], dict=True, exclude=(x,)) |
|
except NotImplementedError: |
|
pass |
|
slns = [s for s in slns if s not in slns0] |
|
if not slns: |
|
return res |
|
if len(slns) > 1: |
|
eqs = [] |
|
for sub_dict in slns: |
|
eqs.extend([Eq(key, value) for key, value in sub_dict.items()]) |
|
slns = solve(eqs, dict=True, exclude=(x,)) + slns |
|
|
|
pairs = [] |
|
for sub_dict in slns: |
|
expr = heurisch(f.subs(sub_dict), x, rewrite, hints, mappings, retries, |
|
degree_offset, unnecessary_permutations, |
|
_try_heurisch) |
|
cond = And(*[Eq(key, value) for key, value in sub_dict.items()]) |
|
generic = Or(*[Ne(key, value) for key, value in sub_dict.items()]) |
|
if expr is None: |
|
expr = integrate(f.subs(sub_dict),x) |
|
pairs.append((expr, cond)) |
|
|
|
|
|
if len(pairs) == 1: |
|
pairs = [(heurisch(f, x, rewrite, hints, mappings, retries, |
|
degree_offset, unnecessary_permutations, |
|
_try_heurisch), |
|
generic), |
|
(pairs[0][0], True)] |
|
else: |
|
pairs.append((heurisch(f, x, rewrite, hints, mappings, retries, |
|
degree_offset, unnecessary_permutations, |
|
_try_heurisch), |
|
True)) |
|
return Piecewise(*pairs) |
|
|
|
class BesselTable: |
|
""" |
|
Derivatives of Bessel functions of orders n and n-1 |
|
in terms of each other. |
|
|
|
See the docstring of DiffCache. |
|
""" |
|
|
|
def __init__(self): |
|
self.table = {} |
|
self.n = Dummy('n') |
|
self.z = Dummy('z') |
|
self._create_table() |
|
|
|
def _create_table(t): |
|
table, n, z = t.table, t.n, t.z |
|
for f in (besselj, bessely, hankel1, hankel2): |
|
table[f] = (f(n-1, z) - n*f(n, z)/z, |
|
(n-1)*f(n-1, z)/z - f(n, z)) |
|
|
|
f = besseli |
|
table[f] = (f(n-1, z) - n*f(n, z)/z, |
|
(n-1)*f(n-1, z)/z + f(n, z)) |
|
f = besselk |
|
table[f] = (-f(n-1, z) - n*f(n, z)/z, |
|
(n-1)*f(n-1, z)/z - f(n, z)) |
|
|
|
for f in (jn, yn): |
|
table[f] = (f(n-1, z) - (n+1)*f(n, z)/z, |
|
(n-1)*f(n-1, z)/z - f(n, z)) |
|
|
|
def diffs(t, f, n, z): |
|
if f in t.table: |
|
diff0, diff1 = t.table[f] |
|
repl = [(t.n, n), (t.z, z)] |
|
return (diff0.subs(repl), diff1.subs(repl)) |
|
|
|
def has(t, f): |
|
return f in t.table |
|
|
|
_bessel_table = None |
|
|
|
class DiffCache: |
|
""" |
|
Store for derivatives of expressions. |
|
|
|
Explanation |
|
=========== |
|
|
|
The standard form of the derivative of a Bessel function of order n |
|
contains two Bessel functions of orders n-1 and n+1, respectively. |
|
Such forms cannot be used in parallel Risch algorithm, because |
|
there is a linear recurrence relation between the three functions |
|
while the algorithm expects that functions and derivatives are |
|
represented in terms of algebraically independent transcendentals. |
|
|
|
The solution is to take two of the functions, e.g., those of orders |
|
n and n-1, and to express the derivatives in terms of the pair. |
|
To guarantee that the proper form is used the two derivatives are |
|
cached as soon as one is encountered. |
|
|
|
Derivatives of other functions are also cached at no extra cost. |
|
All derivatives are with respect to the same variable `x`. |
|
""" |
|
|
|
def __init__(self, x): |
|
self.cache = {} |
|
self.x = x |
|
|
|
global _bessel_table |
|
if not _bessel_table: |
|
_bessel_table = BesselTable() |
|
|
|
def get_diff(self, f): |
|
cache = self.cache |
|
|
|
if f in cache: |
|
pass |
|
elif (not hasattr(f, 'func') or |
|
not _bessel_table.has(f.func)): |
|
cache[f] = cancel(f.diff(self.x)) |
|
else: |
|
n, z = f.args |
|
d0, d1 = _bessel_table.diffs(f.func, n, z) |
|
dz = self.get_diff(z) |
|
cache[f] = d0*dz |
|
cache[f.func(n-1, z)] = d1*dz |
|
|
|
return cache[f] |
|
|
|
def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3, |
|
degree_offset=0, unnecessary_permutations=None, |
|
_try_heurisch=None): |
|
""" |
|
Compute indefinite integral using heuristic Risch algorithm. |
|
|
|
Explanation |
|
=========== |
|
|
|
This is a heuristic approach to indefinite integration in finite |
|
terms using the extended heuristic (parallel) Risch algorithm, based |
|
on Manuel Bronstein's "Poor Man's Integrator". |
|
|
|
The algorithm supports various classes of functions including |
|
transcendental elementary or special functions like Airy, |
|
Bessel, Whittaker and Lambert. |
|
|
|
Note that this algorithm is not a decision procedure. If it isn't |
|
able to compute the antiderivative for a given function, then this is |
|
not a proof that such a functions does not exist. One should use |
|
recursive Risch algorithm in such case. It's an open question if |
|
this algorithm can be made a full decision procedure. |
|
|
|
This is an internal integrator procedure. You should use top level |
|
'integrate' function in most cases, as this procedure needs some |
|
preprocessing steps and otherwise may fail. |
|
|
|
Specification |
|
============= |
|
|
|
heurisch(f, x, rewrite=False, hints=None) |
|
|
|
where |
|
f : expression |
|
x : symbol |
|
|
|
rewrite -> force rewrite 'f' in terms of 'tan' and 'tanh' |
|
hints -> a list of functions that may appear in anti-derivate |
|
|
|
- hints = None --> no suggestions at all |
|
- hints = [ ] --> try to figure out |
|
- hints = [f1, ..., fn] --> we know better |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import tan |
|
>>> from sympy.integrals.heurisch import heurisch |
|
>>> from sympy.abc import x, y |
|
|
|
>>> heurisch(y*tan(x), x) |
|
y*log(tan(x)**2 + 1)/2 |
|
|
|
See Manuel Bronstein's "Poor Man's Integrator": |
|
|
|
References |
|
========== |
|
|
|
.. [1] https://www-sop.inria.fr/cafe/Manuel.Bronstein/pmint/index.html |
|
|
|
For more information on the implemented algorithm refer to: |
|
|
|
.. [2] K. Geddes, L. Stefanus, On the Risch-Norman Integration |
|
Method and its Implementation in Maple, Proceedings of |
|
ISSAC'89, ACM Press, 212-217. |
|
|
|
.. [3] J. H. Davenport, On the Parallel Risch Algorithm (I), |
|
Proceedings of EUROCAM'82, LNCS 144, Springer, 144-157. |
|
|
|
.. [4] J. H. Davenport, On the Parallel Risch Algorithm (III): |
|
Use of Tangents, SIGSAM Bulletin 16 (1982), 3-6. |
|
|
|
.. [5] J. H. Davenport, B. M. Trager, On the Parallel Risch |
|
Algorithm (II), ACM Transactions on Mathematical |
|
Software 11 (1985), 356-362. |
|
|
|
See Also |
|
======== |
|
|
|
sympy.integrals.integrals.Integral.doit |
|
sympy.integrals.integrals.Integral |
|
sympy.integrals.heurisch.components |
|
""" |
|
f = sympify(f) |
|
|
|
|
|
|
|
|
|
if _try_heurisch is not True: |
|
if f.has(Abs, re, im, sign, Heaviside, DiracDelta, floor, ceiling, arg): |
|
return |
|
|
|
if not f.has_free(x): |
|
return f*x |
|
|
|
if not f.is_Add: |
|
indep, f = f.as_independent(x) |
|
else: |
|
indep = S.One |
|
|
|
rewritables = { |
|
(sin, cos, cot): tan, |
|
(sinh, cosh, coth): tanh, |
|
} |
|
|
|
if rewrite: |
|
for candidates, rule in rewritables.items(): |
|
f = f.rewrite(candidates, rule) |
|
else: |
|
for candidates in rewritables.keys(): |
|
if f.has(*candidates): |
|
break |
|
else: |
|
rewrite = True |
|
|
|
terms = components(f, x) |
|
dcache = DiffCache(x) |
|
|
|
if hints is not None: |
|
if not hints: |
|
a = Wild('a', exclude=[x]) |
|
b = Wild('b', exclude=[x]) |
|
c = Wild('c', exclude=[x]) |
|
|
|
for g in set(terms): |
|
if g.is_Function: |
|
if isinstance(g, li): |
|
M = g.args[0].match(a*x**b) |
|
|
|
if M is not None: |
|
terms.add( x*(li(M[a]*x**M[b]) - (M[a]*x**M[b])**(-1/M[b])*Ei((M[b]+1)*log(M[a]*x**M[b])/M[b])) ) |
|
|
|
|
|
|
|
|
|
elif isinstance(g, exp): |
|
M = g.args[0].match(a*x**2) |
|
|
|
if M is not None: |
|
if M[a].is_positive: |
|
terms.add(erfi(sqrt(M[a])*x)) |
|
else: |
|
terms.add(erf(sqrt(-M[a])*x)) |
|
|
|
M = g.args[0].match(a*x**2 + b*x + c) |
|
|
|
if M is not None: |
|
if M[a].is_positive: |
|
terms.add(sqrt(pi/4*(-M[a]))*exp(M[c] - M[b]**2/(4*M[a]))* |
|
erfi(sqrt(M[a])*x + M[b]/(2*sqrt(M[a])))) |
|
elif M[a].is_negative: |
|
terms.add(sqrt(pi/4*(-M[a]))*exp(M[c] - M[b]**2/(4*M[a]))* |
|
erf(sqrt(-M[a])*x - M[b]/(2*sqrt(-M[a])))) |
|
|
|
M = g.args[0].match(a*log(x)**2) |
|
|
|
if M is not None: |
|
if M[a].is_positive: |
|
terms.add(erfi(sqrt(M[a])*log(x) + 1/(2*sqrt(M[a])))) |
|
if M[a].is_negative: |
|
terms.add(erf(sqrt(-M[a])*log(x) - 1/(2*sqrt(-M[a])))) |
|
|
|
elif g.is_Pow: |
|
if g.exp.is_Rational and g.exp.q == 2: |
|
M = g.base.match(a*x**2 + b) |
|
|
|
if M is not None and M[b].is_positive: |
|
if M[a].is_positive: |
|
terms.add(asinh(sqrt(M[a]/M[b])*x)) |
|
elif M[a].is_negative: |
|
terms.add(asin(sqrt(-M[a]/M[b])*x)) |
|
|
|
M = g.base.match(a*x**2 - b) |
|
|
|
if M is not None and M[b].is_positive: |
|
if M[a].is_positive: |
|
dF = 1/sqrt(M[a]*x**2 - M[b]) |
|
F = log(2*sqrt(M[a])*sqrt(M[a]*x**2 - M[b]) + 2*M[a]*x)/sqrt(M[a]) |
|
dcache.cache[F] = dF |
|
terms.add(F) |
|
elif M[a].is_negative: |
|
terms.add(-M[b]/2*sqrt(-M[a])* |
|
atan(sqrt(-M[a])*x/sqrt(M[a]*x**2 - M[b]))) |
|
|
|
else: |
|
terms |= set(hints) |
|
|
|
for g in set(terms): |
|
terms |= components(dcache.get_diff(g), x) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
V = _symbols('x', len(terms)) |
|
|
|
|
|
|
|
mapping = list(reversed(list(zip(*ordered( |
|
[(a[0].as_independent(x)[1], a) for a in zip(terms, V)])))[1])) |
|
rev_mapping = {v: k for k, v in mapping} |
|
if mappings is None: |
|
|
|
assert mapping[-1][0] == x |
|
unnecessary_permutations = [mapping.pop(-1)] |
|
|
|
types = defaultdict(list) |
|
for i in mapping: |
|
e, _ = i |
|
types[type(e)].append(i) |
|
mapping = [types[i] for i in types] |
|
def _iter_mappings(): |
|
for i in permutations(mapping): |
|
|
|
yield [j for i in i for j in ordered(i)] |
|
mappings = _iter_mappings() |
|
else: |
|
unnecessary_permutations = unnecessary_permutations or [] |
|
|
|
def _substitute(expr): |
|
return expr.subs(mapping) |
|
|
|
for mapping in mappings: |
|
mapping = list(mapping) |
|
mapping = mapping + unnecessary_permutations |
|
diffs = [ _substitute(dcache.get_diff(g)) for g in terms ] |
|
denoms = [ g.as_numer_denom()[1] for g in diffs ] |
|
if all(h.is_polynomial(*V) for h in denoms) and _substitute(f).is_rational_function(*V): |
|
denom = reduce(lambda p, q: lcm(p, q, *V), denoms) |
|
break |
|
else: |
|
if not rewrite: |
|
result = heurisch(f, x, rewrite=True, hints=hints, |
|
unnecessary_permutations=unnecessary_permutations) |
|
|
|
if result is not None: |
|
return indep*result |
|
return None |
|
|
|
numers = [ cancel(denom*g) for g in diffs ] |
|
def _derivation(h): |
|
return Add(*[ d * h.diff(v) for d, v in zip(numers, V) ]) |
|
|
|
def _deflation(p): |
|
for y in V: |
|
if not p.has(y): |
|
continue |
|
|
|
if _derivation(p) is not S.Zero: |
|
c, q = p.as_poly(y).primitive() |
|
return _deflation(c)*gcd(q, q.diff(y)).as_expr() |
|
|
|
return p |
|
|
|
def _splitter(p): |
|
for y in V: |
|
if not p.has(y): |
|
continue |
|
|
|
if _derivation(y) is not S.Zero: |
|
c, q = p.as_poly(y).primitive() |
|
|
|
q = q.as_expr() |
|
|
|
h = gcd(q, _derivation(q), y) |
|
s = quo(h, gcd(q, q.diff(y), y), y) |
|
|
|
c_split = _splitter(c) |
|
|
|
if s.as_poly(y).degree() == 0: |
|
return (c_split[0], q * c_split[1]) |
|
|
|
q_split = _splitter(cancel(q / s)) |
|
|
|
return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1]) |
|
|
|
return (S.One, p) |
|
|
|
special = {} |
|
|
|
for term in terms: |
|
if term.is_Function: |
|
if isinstance(term, tan): |
|
special[1 + _substitute(term)**2] = False |
|
elif isinstance(term, tanh): |
|
special[1 + _substitute(term)] = False |
|
special[1 - _substitute(term)] = False |
|
elif isinstance(term, LambertW): |
|
special[_substitute(term)] = True |
|
|
|
F = _substitute(f) |
|
|
|
P, Q = F.as_numer_denom() |
|
|
|
u_split = _splitter(denom) |
|
v_split = _splitter(Q) |
|
|
|
polys = set(list(v_split) + [ u_split[0] ] + list(special.keys())) |
|
|
|
s = u_split[0] * Mul(*[ k for k, v in special.items() if v ]) |
|
polified = [ p.as_poly(*V) for p in [s, P, Q] ] |
|
|
|
if None in polified: |
|
return None |
|
|
|
|
|
a, b, c = [ p.total_degree() for p in polified ] |
|
|
|
poly_denom = (s * v_split[0] * _deflation(v_split[1])).as_expr() |
|
|
|
def _exponent(g): |
|
if g.is_Pow: |
|
if g.exp.is_Rational and g.exp.q != 1: |
|
if g.exp.p > 0: |
|
return g.exp.p + g.exp.q - 1 |
|
else: |
|
return abs(g.exp.p + g.exp.q) |
|
else: |
|
return 1 |
|
elif not g.is_Atom and g.args: |
|
return max(_exponent(h) for h in g.args) |
|
else: |
|
return 1 |
|
|
|
A, B = _exponent(f), a + max(b, c) |
|
|
|
if A > 1 and B > 1: |
|
monoms = tuple(ordered(itermonomials(V, A + B - 1 + degree_offset))) |
|
else: |
|
monoms = tuple(ordered(itermonomials(V, A + B + degree_offset))) |
|
|
|
poly_coeffs = _symbols('A', len(monoms)) |
|
|
|
poly_part = Add(*[ poly_coeffs[i]*monomial |
|
for i, monomial in enumerate(monoms) ]) |
|
|
|
reducibles = set() |
|
|
|
for poly in ordered(polys): |
|
coeff, factors = factor_list(poly, *V) |
|
reducibles.add(coeff) |
|
reducibles.update(fact for fact, mul in factors) |
|
|
|
def _integrate(field=None): |
|
atans = set() |
|
pairs = set() |
|
|
|
if field == 'Q': |
|
irreducibles = set(reducibles) |
|
else: |
|
setV = set(V) |
|
irreducibles = set() |
|
for poly in ordered(reducibles): |
|
zV = setV & set(iterfreeargs(poly)) |
|
for z in ordered(zV): |
|
s = set(root_factors(poly, z, filter=field)) |
|
irreducibles |= s |
|
break |
|
|
|
log_part, atan_part = [], [] |
|
|
|
for poly in ordered(irreducibles): |
|
m = collect(poly, I, evaluate=False) |
|
y = m.get(I, S.Zero) |
|
if y: |
|
x = m.get(S.One, S.Zero) |
|
if x.has(I) or y.has(I): |
|
continue |
|
pairs.add((x, y)) |
|
irreducibles.remove(poly) |
|
|
|
while pairs: |
|
x, y = pairs.pop() |
|
if (x, -y) in pairs: |
|
pairs.remove((x, -y)) |
|
|
|
if y.could_extract_minus_sign(): |
|
y = -y |
|
irreducibles.add(x*x + y*y) |
|
atans.add(atan(x/y)) |
|
else: |
|
irreducibles.add(x + I*y) |
|
|
|
|
|
B = _symbols('B', len(irreducibles)) |
|
C = _symbols('C', len(atans)) |
|
|
|
|
|
for poly, b in reversed(list(zip(ordered(irreducibles), B))): |
|
if poly.has(*V): |
|
poly_coeffs.append(b) |
|
log_part.append(b * log(poly)) |
|
|
|
for poly, c in reversed(list(zip(ordered(atans), C))): |
|
if poly.has(*V): |
|
poly_coeffs.append(c) |
|
atan_part.append(c * poly) |
|
|
|
|
|
|
|
|
|
|
|
|
|
candidate = poly_part/poly_denom + Add(*log_part) + Add(*atan_part) |
|
h = F - _derivation(candidate) / denom |
|
raw_numer = h.as_numer_denom()[0] |
|
|
|
|
|
|
|
|
|
|
|
syms = set(poly_coeffs) | set(V) |
|
non_syms = set() |
|
|
|
def find_non_syms(expr): |
|
if expr.is_Integer or expr.is_Rational: |
|
pass |
|
elif expr in syms: |
|
pass |
|
elif not expr.has_free(*syms): |
|
non_syms.add(expr) |
|
elif expr.is_Add or expr.is_Mul or expr.is_Pow: |
|
list(map(find_non_syms, expr.args)) |
|
else: |
|
|
|
|
|
raise PolynomialError |
|
|
|
try: |
|
find_non_syms(raw_numer) |
|
except PolynomialError: |
|
return None |
|
else: |
|
ground, _ = construct_domain(non_syms, field=True) |
|
|
|
coeff_ring = PolyRing(poly_coeffs, ground) |
|
ring = PolyRing(V, coeff_ring) |
|
try: |
|
numer = ring.from_expr(raw_numer) |
|
except ValueError: |
|
raise PolynomialError |
|
solution = solve_lin_sys(numer.coeffs(), coeff_ring, _raw=False) |
|
|
|
if solution is None: |
|
return None |
|
else: |
|
return candidate.xreplace(solution).xreplace( |
|
dict(zip(poly_coeffs, [S.Zero]*len(poly_coeffs)))) |
|
|
|
if all(isinstance(_, Symbol) for _ in V): |
|
more_free = F.free_symbols - set(V) |
|
else: |
|
Fd = F.as_dummy() |
|
more_free = Fd.xreplace(dict(zip(V, (Dummy() for _ in V))) |
|
).free_symbols & Fd.free_symbols |
|
if not more_free: |
|
|
|
solution = _integrate('Q') |
|
|
|
if solution is None: |
|
solution = _integrate() |
|
else: |
|
solution = _integrate() |
|
|
|
if solution is not None: |
|
antideriv = solution.subs(rev_mapping) |
|
antideriv = cancel(antideriv).expand() |
|
|
|
if antideriv.is_Add: |
|
antideriv = antideriv.as_independent(x)[1] |
|
|
|
return indep*antideriv |
|
else: |
|
if retries >= 0: |
|
result = heurisch(f, x, mappings=mappings, rewrite=rewrite, hints=hints, retries=retries - 1, unnecessary_permutations=unnecessary_permutations) |
|
|
|
if result is not None: |
|
return indep*result |
|
|
|
return None |
|
|