You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
801 lines
27 KiB
801 lines
27 KiB
5 months ago
|
"""
|
||
|
Algorithms for solving the Risch differential equation.
|
||
|
|
||
|
Given a differential field K of characteristic 0 that is a simple
|
||
|
monomial extension of a base field k and f, g in K, the Risch
|
||
|
Differential Equation problem is to decide if there exist y in K such
|
||
|
that Dy + f*y == g and to find one if there are some. If t is a
|
||
|
monomial over k and the coefficients of f and g are in k(t), then y is
|
||
|
in k(t), and the outline of the algorithm here is given as:
|
||
|
|
||
|
1. Compute the normal part n of the denominator of y. The problem is
|
||
|
then reduced to finding y' in k<t>, where y == y'/n.
|
||
|
2. Compute the special part s of the denominator of y. The problem is
|
||
|
then reduced to finding y'' in k[t], where y == y''/(n*s)
|
||
|
3. Bound the degree of y''.
|
||
|
4. Reduce the equation Dy + f*y == g to a similar equation with f, g in
|
||
|
k[t].
|
||
|
5. Find the solutions in k[t] of bounded degree of the reduced equation.
|
||
|
|
||
|
See Chapter 6 of "Symbolic Integration I: Transcendental Functions" by
|
||
|
Manuel Bronstein. See also the docstring of risch.py.
|
||
|
"""
|
||
|
|
||
|
from operator import mul
|
||
|
from functools import reduce
|
||
|
|
||
|
from sympy.core import oo
|
||
|
from sympy.core.symbol import Dummy
|
||
|
|
||
|
from sympy.polys import Poly, gcd, ZZ, cancel
|
||
|
|
||
|
from sympy.functions.elementary.complexes import (im, re)
|
||
|
from sympy.functions.elementary.miscellaneous import sqrt
|
||
|
|
||
|
from sympy.integrals.risch import (gcdex_diophantine, frac_in, derivation,
|
||
|
splitfactor, NonElementaryIntegralException, DecrementLevel, recognize_log_derivative)
|
||
|
|
||
|
# TODO: Add messages to NonElementaryIntegralException errors
|
||
|
|
||
|
|
||
|
def order_at(a, p, t):
|
||
|
"""
|
||
|
Computes the order of a at p, with respect to t.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
For a, p in k[t], the order of a at p is defined as nu_p(a) = max({n
|
||
|
in Z+ such that p**n|a}), where a != 0. If a == 0, nu_p(a) = +oo.
|
||
|
|
||
|
To compute the order at a rational function, a/b, use the fact that
|
||
|
nu_p(a/b) == nu_p(a) - nu_p(b).
|
||
|
"""
|
||
|
if a.is_zero:
|
||
|
return oo
|
||
|
if p == Poly(t, t):
|
||
|
return a.as_poly(t).ET()[0][0]
|
||
|
|
||
|
# Uses binary search for calculating the power. power_list collects the tuples
|
||
|
# (p^k,k) where each k is some power of 2. After deciding the largest k
|
||
|
# such that k is power of 2 and p^k|a the loop iteratively calculates
|
||
|
# the actual power.
|
||
|
power_list = []
|
||
|
p1 = p
|
||
|
r = a.rem(p1)
|
||
|
tracks_power = 1
|
||
|
while r.is_zero:
|
||
|
power_list.append((p1,tracks_power))
|
||
|
p1 = p1*p1
|
||
|
tracks_power *= 2
|
||
|
r = a.rem(p1)
|
||
|
n = 0
|
||
|
product = Poly(1, t)
|
||
|
while len(power_list) != 0:
|
||
|
final = power_list.pop()
|
||
|
productf = product*final[0]
|
||
|
r = a.rem(productf)
|
||
|
if r.is_zero:
|
||
|
n += final[1]
|
||
|
product = productf
|
||
|
return n
|
||
|
|
||
|
|
||
|
def order_at_oo(a, d, t):
|
||
|
"""
|
||
|
Computes the order of a/d at oo (infinity), with respect to t.
|
||
|
|
||
|
For f in k(t), the order or f at oo is defined as deg(d) - deg(a), where
|
||
|
f == a/d.
|
||
|
"""
|
||
|
if a.is_zero:
|
||
|
return oo
|
||
|
return d.degree(t) - a.degree(t)
|
||
|
|
||
|
|
||
|
def weak_normalizer(a, d, DE, z=None):
|
||
|
"""
|
||
|
Weak normalization.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Given a derivation D on k[t] and f == a/d in k(t), return q in k[t]
|
||
|
such that f - Dq/q is weakly normalized with respect to t.
|
||
|
|
||
|
f in k(t) is said to be "weakly normalized" with respect to t if
|
||
|
residue_p(f) is not a positive integer for any normal irreducible p
|
||
|
in k[t] such that f is in R_p (Definition 6.1.1). If f has an
|
||
|
elementary integral, this is equivalent to no logarithm of
|
||
|
integral(f) whose argument depends on t has a positive integer
|
||
|
coefficient, where the arguments of the logarithms not in k(t) are
|
||
|
in k[t].
|
||
|
|
||
|
Returns (q, f - Dq/q)
|
||
|
"""
|
||
|
z = z or Dummy('z')
|
||
|
dn, ds = splitfactor(d, DE)
|
||
|
|
||
|
# Compute d1, where dn == d1*d2**2*...*dn**n is a square-free
|
||
|
# factorization of d.
|
||
|
g = gcd(dn, dn.diff(DE.t))
|
||
|
d_sqf_part = dn.quo(g)
|
||
|
d1 = d_sqf_part.quo(gcd(d_sqf_part, g))
|
||
|
|
||
|
a1, b = gcdex_diophantine(d.quo(d1).as_poly(DE.t), d1.as_poly(DE.t),
|
||
|
a.as_poly(DE.t))
|
||
|
r = (a - Poly(z, DE.t)*derivation(d1, DE)).as_poly(DE.t).resultant(
|
||
|
d1.as_poly(DE.t))
|
||
|
r = Poly(r, z)
|
||
|
|
||
|
if not r.expr.has(z):
|
||
|
return (Poly(1, DE.t), (a, d))
|
||
|
|
||
|
N = [i for i in r.real_roots() if i in ZZ and i > 0]
|
||
|
|
||
|
q = reduce(mul, [gcd(a - Poly(n, DE.t)*derivation(d1, DE), d1) for n in N],
|
||
|
Poly(1, DE.t))
|
||
|
|
||
|
dq = derivation(q, DE)
|
||
|
sn = q*a - d*dq
|
||
|
sd = q*d
|
||
|
sn, sd = sn.cancel(sd, include=True)
|
||
|
|
||
|
return (q, (sn, sd))
|
||
|
|
||
|
|
||
|
def normal_denom(fa, fd, ga, gd, DE):
|
||
|
"""
|
||
|
Normal part of the denominator.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Given a derivation D on k[t] and f, g in k(t) with f weakly
|
||
|
normalized with respect to t, either raise NonElementaryIntegralException,
|
||
|
in which case the equation Dy + f*y == g has no solution in k(t), or the
|
||
|
quadruplet (a, b, c, h) such that a, h in k[t], b, c in k<t>, and for any
|
||
|
solution y in k(t) of Dy + f*y == g, q = y*h in k<t> satisfies
|
||
|
a*Dq + b*q == c.
|
||
|
|
||
|
This constitutes step 1 in the outline given in the rde.py docstring.
|
||
|
"""
|
||
|
dn, ds = splitfactor(fd, DE)
|
||
|
en, es = splitfactor(gd, DE)
|
||
|
|
||
|
p = dn.gcd(en)
|
||
|
h = en.gcd(en.diff(DE.t)).quo(p.gcd(p.diff(DE.t)))
|
||
|
|
||
|
a = dn*h
|
||
|
c = a*h
|
||
|
if c.div(en)[1]:
|
||
|
# en does not divide dn*h**2
|
||
|
raise NonElementaryIntegralException
|
||
|
ca = c*ga
|
||
|
ca, cd = ca.cancel(gd, include=True)
|
||
|
|
||
|
ba = a*fa - dn*derivation(h, DE)*fd
|
||
|
ba, bd = ba.cancel(fd, include=True)
|
||
|
|
||
|
# (dn*h, dn*h*f - dn*Dh, dn*h**2*g, h)
|
||
|
return (a, (ba, bd), (ca, cd), h)
|
||
|
|
||
|
|
||
|
def special_denom(a, ba, bd, ca, cd, DE, case='auto'):
|
||
|
"""
|
||
|
Special part of the denominator.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
case is one of {'exp', 'tan', 'primitive'} for the hyperexponential,
|
||
|
hypertangent, and primitive cases, respectively. For the
|
||
|
hyperexponential (resp. hypertangent) case, given a derivation D on
|
||
|
k[t] and a in k[t], b, c, in k<t> with Dt/t in k (resp. Dt/(t**2 + 1) in
|
||
|
k, sqrt(-1) not in k), a != 0, and gcd(a, t) == 1 (resp.
|
||
|
gcd(a, t**2 + 1) == 1), return the quadruplet (A, B, C, 1/h) such that
|
||
|
A, B, C, h in k[t] and for any solution q in k<t> of a*Dq + b*q == c,
|
||
|
r = qh in k[t] satisfies A*Dr + B*r == C.
|
||
|
|
||
|
For ``case == 'primitive'``, k<t> == k[t], so it returns (a, b, c, 1) in
|
||
|
this case.
|
||
|
|
||
|
This constitutes step 2 of the outline given in the rde.py docstring.
|
||
|
"""
|
||
|
# TODO: finish writing this and write tests
|
||
|
|
||
|
if case == 'auto':
|
||
|
case = DE.case
|
||
|
|
||
|
if case == 'exp':
|
||
|
p = Poly(DE.t, DE.t)
|
||
|
elif case == 'tan':
|
||
|
p = Poly(DE.t**2 + 1, DE.t)
|
||
|
elif case in ('primitive', 'base'):
|
||
|
B = ba.to_field().quo(bd)
|
||
|
C = ca.to_field().quo(cd)
|
||
|
return (a, B, C, Poly(1, DE.t))
|
||
|
else:
|
||
|
raise ValueError("case must be one of {'exp', 'tan', 'primitive', "
|
||
|
"'base'}, not %s." % case)
|
||
|
|
||
|
nb = order_at(ba, p, DE.t) - order_at(bd, p, DE.t)
|
||
|
nc = order_at(ca, p, DE.t) - order_at(cd, p, DE.t)
|
||
|
|
||
|
n = min(0, nc - min(0, nb))
|
||
|
if not nb:
|
||
|
# Possible cancellation.
|
||
|
from .prde import parametric_log_deriv
|
||
|
if case == 'exp':
|
||
|
dcoeff = DE.d.quo(Poly(DE.t, DE.t))
|
||
|
with DecrementLevel(DE): # We are guaranteed to not have problems,
|
||
|
# because case != 'base'.
|
||
|
alphaa, alphad = frac_in(-ba.eval(0)/bd.eval(0)/a.eval(0), DE.t)
|
||
|
etaa, etad = frac_in(dcoeff, DE.t)
|
||
|
A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE)
|
||
|
if A is not None:
|
||
|
Q, m, z = A
|
||
|
if Q == 1:
|
||
|
n = min(n, m)
|
||
|
|
||
|
elif case == 'tan':
|
||
|
dcoeff = DE.d.quo(Poly(DE.t**2+1, DE.t))
|
||
|
with DecrementLevel(DE): # We are guaranteed to not have problems,
|
||
|
# because case != 'base'.
|
||
|
alphaa, alphad = frac_in(im(-ba.eval(sqrt(-1))/bd.eval(sqrt(-1))/a.eval(sqrt(-1))), DE.t)
|
||
|
betaa, betad = frac_in(re(-ba.eval(sqrt(-1))/bd.eval(sqrt(-1))/a.eval(sqrt(-1))), DE.t)
|
||
|
etaa, etad = frac_in(dcoeff, DE.t)
|
||
|
|
||
|
if recognize_log_derivative(Poly(2, DE.t)*betaa, betad, DE):
|
||
|
A = parametric_log_deriv(alphaa*Poly(sqrt(-1), DE.t)*betad+alphad*betaa, alphad*betad, etaa, etad, DE)
|
||
|
if A is not None:
|
||
|
Q, m, z = A
|
||
|
if Q == 1:
|
||
|
n = min(n, m)
|
||
|
N = max(0, -nb, n - nc)
|
||
|
pN = p**N
|
||
|
pn = p**-n
|
||
|
|
||
|
A = a*pN
|
||
|
B = ba*pN.quo(bd) + Poly(n, DE.t)*a*derivation(p, DE).quo(p)*pN
|
||
|
C = (ca*pN*pn).quo(cd)
|
||
|
h = pn
|
||
|
|
||
|
# (a*p**N, (b + n*a*Dp/p)*p**N, c*p**(N - n), p**-n)
|
||
|
return (A, B, C, h)
|
||
|
|
||
|
|
||
|
def bound_degree(a, b, cQ, DE, case='auto', parametric=False):
|
||
|
"""
|
||
|
Bound on polynomial solutions.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Given a derivation D on k[t] and ``a``, ``b``, ``c`` in k[t] with ``a != 0``, return
|
||
|
n in ZZ such that deg(q) <= n for any solution q in k[t] of
|
||
|
a*Dq + b*q == c, when parametric=False, or deg(q) <= n for any solution
|
||
|
c1, ..., cm in Const(k) and q in k[t] of a*Dq + b*q == Sum(ci*gi, (i, 1, m))
|
||
|
when parametric=True.
|
||
|
|
||
|
For ``parametric=False``, ``cQ`` is ``c``, a ``Poly``; for ``parametric=True``, ``cQ`` is Q ==
|
||
|
[q1, ..., qm], a list of Polys.
|
||
|
|
||
|
This constitutes step 3 of the outline given in the rde.py docstring.
|
||
|
"""
|
||
|
# TODO: finish writing this and write tests
|
||
|
|
||
|
if case == 'auto':
|
||
|
case = DE.case
|
||
|
|
||
|
da = a.degree(DE.t)
|
||
|
db = b.degree(DE.t)
|
||
|
|
||
|
# The parametric and regular cases are identical, except for this part
|
||
|
if parametric:
|
||
|
dc = max([i.degree(DE.t) for i in cQ])
|
||
|
else:
|
||
|
dc = cQ.degree(DE.t)
|
||
|
|
||
|
alpha = cancel(-b.as_poly(DE.t).LC().as_expr()/
|
||
|
a.as_poly(DE.t).LC().as_expr())
|
||
|
|
||
|
if case == 'base':
|
||
|
n = max(0, dc - max(db, da - 1))
|
||
|
if db == da - 1 and alpha.is_Integer:
|
||
|
n = max(0, alpha, dc - db)
|
||
|
|
||
|
elif case == 'primitive':
|
||
|
if db > da:
|
||
|
n = max(0, dc - db)
|
||
|
else:
|
||
|
n = max(0, dc - da + 1)
|
||
|
|
||
|
etaa, etad = frac_in(DE.d, DE.T[DE.level - 1])
|
||
|
|
||
|
t1 = DE.t
|
||
|
with DecrementLevel(DE):
|
||
|
alphaa, alphad = frac_in(alpha, DE.t)
|
||
|
if db == da - 1:
|
||
|
from .prde import limited_integrate
|
||
|
# if alpha == m*Dt + Dz for z in k and m in ZZ:
|
||
|
try:
|
||
|
(za, zd), m = limited_integrate(alphaa, alphad, [(etaa, etad)],
|
||
|
DE)
|
||
|
except NonElementaryIntegralException:
|
||
|
pass
|
||
|
else:
|
||
|
if len(m) != 1:
|
||
|
raise ValueError("Length of m should be 1")
|
||
|
n = max(n, m[0])
|
||
|
|
||
|
elif db == da:
|
||
|
# if alpha == Dz/z for z in k*:
|
||
|
# beta = -lc(a*Dz + b*z)/(z*lc(a))
|
||
|
# if beta == m*Dt + Dw for w in k and m in ZZ:
|
||
|
# n = max(n, m)
|
||
|
from .prde import is_log_deriv_k_t_radical_in_field
|
||
|
A = is_log_deriv_k_t_radical_in_field(alphaa, alphad, DE)
|
||
|
if A is not None:
|
||
|
aa, z = A
|
||
|
if aa == 1:
|
||
|
beta = -(a*derivation(z, DE).as_poly(t1) +
|
||
|
b*z.as_poly(t1)).LC()/(z.as_expr()*a.LC())
|
||
|
betaa, betad = frac_in(beta, DE.t)
|
||
|
from .prde import limited_integrate
|
||
|
try:
|
||
|
(za, zd), m = limited_integrate(betaa, betad,
|
||
|
[(etaa, etad)], DE)
|
||
|
except NonElementaryIntegralException:
|
||
|
pass
|
||
|
else:
|
||
|
if len(m) != 1:
|
||
|
raise ValueError("Length of m should be 1")
|
||
|
n = max(n, m[0].as_expr())
|
||
|
|
||
|
elif case == 'exp':
|
||
|
from .prde import parametric_log_deriv
|
||
|
|
||
|
n = max(0, dc - max(db, da))
|
||
|
if da == db:
|
||
|
etaa, etad = frac_in(DE.d.quo(Poly(DE.t, DE.t)), DE.T[DE.level - 1])
|
||
|
with DecrementLevel(DE):
|
||
|
alphaa, alphad = frac_in(alpha, DE.t)
|
||
|
A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE)
|
||
|
if A is not None:
|
||
|
# if alpha == m*Dt/t + Dz/z for z in k* and m in ZZ:
|
||
|
# n = max(n, m)
|
||
|
a, m, z = A
|
||
|
if a == 1:
|
||
|
n = max(n, m)
|
||
|
|
||
|
elif case in ('tan', 'other_nonlinear'):
|
||
|
delta = DE.d.degree(DE.t)
|
||
|
lam = DE.d.LC()
|
||
|
alpha = cancel(alpha/lam)
|
||
|
n = max(0, dc - max(da + delta - 1, db))
|
||
|
if db == da + delta - 1 and alpha.is_Integer:
|
||
|
n = max(0, alpha, dc - db)
|
||
|
|
||
|
else:
|
||
|
raise ValueError("case must be one of {'exp', 'tan', 'primitive', "
|
||
|
"'other_nonlinear', 'base'}, not %s." % case)
|
||
|
|
||
|
return n
|
||
|
|
||
|
|
||
|
def spde(a, b, c, n, DE):
|
||
|
"""
|
||
|
Rothstein's Special Polynomial Differential Equation algorithm.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Given a derivation D on k[t], an integer n and ``a``,``b``,``c`` in k[t] with
|
||
|
``a != 0``, either raise NonElementaryIntegralException, in which case the
|
||
|
equation a*Dq + b*q == c has no solution of degree at most ``n`` in
|
||
|
k[t], or return the tuple (B, C, m, alpha, beta) such that B, C,
|
||
|
alpha, beta in k[t], m in ZZ, and any solution q in k[t] of degree
|
||
|
at most n of a*Dq + b*q == c must be of the form
|
||
|
q == alpha*h + beta, where h in k[t], deg(h) <= m, and Dh + B*h == C.
|
||
|
|
||
|
This constitutes step 4 of the outline given in the rde.py docstring.
|
||
|
"""
|
||
|
zero = Poly(0, DE.t)
|
||
|
|
||
|
alpha = Poly(1, DE.t)
|
||
|
beta = Poly(0, DE.t)
|
||
|
|
||
|
while True:
|
||
|
if c.is_zero:
|
||
|
return (zero, zero, 0, zero, beta) # -1 is more to the point
|
||
|
if (n < 0) is True:
|
||
|
raise NonElementaryIntegralException
|
||
|
|
||
|
g = a.gcd(b)
|
||
|
if not c.rem(g).is_zero: # g does not divide c
|
||
|
raise NonElementaryIntegralException
|
||
|
|
||
|
a, b, c = a.quo(g), b.quo(g), c.quo(g)
|
||
|
|
||
|
if a.degree(DE.t) == 0:
|
||
|
b = b.to_field().quo(a)
|
||
|
c = c.to_field().quo(a)
|
||
|
return (b, c, n, alpha, beta)
|
||
|
|
||
|
r, z = gcdex_diophantine(b, a, c)
|
||
|
b += derivation(a, DE)
|
||
|
c = z - derivation(r, DE)
|
||
|
n -= a.degree(DE.t)
|
||
|
|
||
|
beta += alpha * r
|
||
|
alpha *= a
|
||
|
|
||
|
def no_cancel_b_large(b, c, n, DE):
|
||
|
"""
|
||
|
Poly Risch Differential Equation - No cancellation: deg(b) large enough.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Given a derivation D on k[t], ``n`` either an integer or +oo, and ``b``,``c``
|
||
|
in k[t] with ``b != 0`` and either D == d/dt or
|
||
|
deg(b) > max(0, deg(D) - 1), either raise NonElementaryIntegralException, in
|
||
|
which case the equation ``Dq + b*q == c`` has no solution of degree at
|
||
|
most n in k[t], or a solution q in k[t] of this equation with
|
||
|
``deg(q) < n``.
|
||
|
"""
|
||
|
q = Poly(0, DE.t)
|
||
|
|
||
|
while not c.is_zero:
|
||
|
m = c.degree(DE.t) - b.degree(DE.t)
|
||
|
if not 0 <= m <= n: # n < 0 or m < 0 or m > n
|
||
|
raise NonElementaryIntegralException
|
||
|
|
||
|
p = Poly(c.as_poly(DE.t).LC()/b.as_poly(DE.t).LC()*DE.t**m, DE.t,
|
||
|
expand=False)
|
||
|
q = q + p
|
||
|
n = m - 1
|
||
|
c = c - derivation(p, DE) - b*p
|
||
|
|
||
|
return q
|
||
|
|
||
|
|
||
|
def no_cancel_b_small(b, c, n, DE):
|
||
|
"""
|
||
|
Poly Risch Differential Equation - No cancellation: deg(b) small enough.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Given a derivation D on k[t], ``n`` either an integer or +oo, and ``b``,``c``
|
||
|
in k[t] with deg(b) < deg(D) - 1 and either D == d/dt or
|
||
|
deg(D) >= 2, either raise NonElementaryIntegralException, in which case the
|
||
|
equation Dq + b*q == c has no solution of degree at most n in k[t],
|
||
|
or a solution q in k[t] of this equation with deg(q) <= n, or the
|
||
|
tuple (h, b0, c0) such that h in k[t], b0, c0, in k, and for any
|
||
|
solution q in k[t] of degree at most n of Dq + bq == c, y == q - h
|
||
|
is a solution in k of Dy + b0*y == c0.
|
||
|
"""
|
||
|
q = Poly(0, DE.t)
|
||
|
|
||
|
while not c.is_zero:
|
||
|
if n == 0:
|
||
|
m = 0
|
||
|
else:
|
||
|
m = c.degree(DE.t) - DE.d.degree(DE.t) + 1
|
||
|
|
||
|
if not 0 <= m <= n: # n < 0 or m < 0 or m > n
|
||
|
raise NonElementaryIntegralException
|
||
|
|
||
|
if m > 0:
|
||
|
p = Poly(c.as_poly(DE.t).LC()/(m*DE.d.as_poly(DE.t).LC())*DE.t**m,
|
||
|
DE.t, expand=False)
|
||
|
else:
|
||
|
if b.degree(DE.t) != c.degree(DE.t):
|
||
|
raise NonElementaryIntegralException
|
||
|
if b.degree(DE.t) == 0:
|
||
|
return (q, b.as_poly(DE.T[DE.level - 1]),
|
||
|
c.as_poly(DE.T[DE.level - 1]))
|
||
|
p = Poly(c.as_poly(DE.t).LC()/b.as_poly(DE.t).LC(), DE.t,
|
||
|
expand=False)
|
||
|
|
||
|
q = q + p
|
||
|
n = m - 1
|
||
|
c = c - derivation(p, DE) - b*p
|
||
|
|
||
|
return q
|
||
|
|
||
|
|
||
|
# TODO: better name for this function
|
||
|
def no_cancel_equal(b, c, n, DE):
|
||
|
"""
|
||
|
Poly Risch Differential Equation - No cancellation: deg(b) == deg(D) - 1
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Given a derivation D on k[t] with deg(D) >= 2, n either an integer
|
||
|
or +oo, and b, c in k[t] with deg(b) == deg(D) - 1, either raise
|
||
|
NonElementaryIntegralException, in which case the equation Dq + b*q == c has
|
||
|
no solution of degree at most n in k[t], or a solution q in k[t] of
|
||
|
this equation with deg(q) <= n, or the tuple (h, m, C) such that h
|
||
|
in k[t], m in ZZ, and C in k[t], and for any solution q in k[t] of
|
||
|
degree at most n of Dq + b*q == c, y == q - h is a solution in k[t]
|
||
|
of degree at most m of Dy + b*y == C.
|
||
|
"""
|
||
|
q = Poly(0, DE.t)
|
||
|
lc = cancel(-b.as_poly(DE.t).LC()/DE.d.as_poly(DE.t).LC())
|
||
|
if lc.is_Integer and lc.is_positive:
|
||
|
M = lc
|
||
|
else:
|
||
|
M = -1
|
||
|
|
||
|
while not c.is_zero:
|
||
|
m = max(M, c.degree(DE.t) - DE.d.degree(DE.t) + 1)
|
||
|
|
||
|
if not 0 <= m <= n: # n < 0 or m < 0 or m > n
|
||
|
raise NonElementaryIntegralException
|
||
|
|
||
|
u = cancel(m*DE.d.as_poly(DE.t).LC() + b.as_poly(DE.t).LC())
|
||
|
if u.is_zero:
|
||
|
return (q, m, c)
|
||
|
if m > 0:
|
||
|
p = Poly(c.as_poly(DE.t).LC()/u*DE.t**m, DE.t, expand=False)
|
||
|
else:
|
||
|
if c.degree(DE.t) != DE.d.degree(DE.t) - 1:
|
||
|
raise NonElementaryIntegralException
|
||
|
else:
|
||
|
p = c.as_poly(DE.t).LC()/b.as_poly(DE.t).LC()
|
||
|
|
||
|
q = q + p
|
||
|
n = m - 1
|
||
|
c = c - derivation(p, DE) - b*p
|
||
|
|
||
|
return q
|
||
|
|
||
|
|
||
|
def cancel_primitive(b, c, n, DE):
|
||
|
"""
|
||
|
Poly Risch Differential Equation - Cancellation: Primitive case.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Given a derivation D on k[t], n either an integer or +oo, ``b`` in k, and
|
||
|
``c`` in k[t] with Dt in k and ``b != 0``, either raise
|
||
|
NonElementaryIntegralException, in which case the equation Dq + b*q == c
|
||
|
has no solution of degree at most n in k[t], or a solution q in k[t] of
|
||
|
this equation with deg(q) <= n.
|
||
|
"""
|
||
|
# Delayed imports
|
||
|
from .prde import is_log_deriv_k_t_radical_in_field
|
||
|
with DecrementLevel(DE):
|
||
|
ba, bd = frac_in(b, DE.t)
|
||
|
A = is_log_deriv_k_t_radical_in_field(ba, bd, DE)
|
||
|
if A is not None:
|
||
|
n, z = A
|
||
|
if n == 1: # b == Dz/z
|
||
|
raise NotImplementedError("is_deriv_in_field() is required to "
|
||
|
" solve this problem.")
|
||
|
# if z*c == Dp for p in k[t] and deg(p) <= n:
|
||
|
# return p/z
|
||
|
# else:
|
||
|
# raise NonElementaryIntegralException
|
||
|
|
||
|
if c.is_zero:
|
||
|
return c # return 0
|
||
|
|
||
|
if n < c.degree(DE.t):
|
||
|
raise NonElementaryIntegralException
|
||
|
|
||
|
q = Poly(0, DE.t)
|
||
|
while not c.is_zero:
|
||
|
m = c.degree(DE.t)
|
||
|
if n < m:
|
||
|
raise NonElementaryIntegralException
|
||
|
with DecrementLevel(DE):
|
||
|
a2a, a2d = frac_in(c.LC(), DE.t)
|
||
|
sa, sd = rischDE(ba, bd, a2a, a2d, DE)
|
||
|
stm = Poly(sa.as_expr()/sd.as_expr()*DE.t**m, DE.t, expand=False)
|
||
|
q += stm
|
||
|
n = m - 1
|
||
|
c -= b*stm + derivation(stm, DE)
|
||
|
|
||
|
return q
|
||
|
|
||
|
|
||
|
def cancel_exp(b, c, n, DE):
|
||
|
"""
|
||
|
Poly Risch Differential Equation - Cancellation: Hyperexponential case.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
Given a derivation D on k[t], n either an integer or +oo, ``b`` in k, and
|
||
|
``c`` in k[t] with Dt/t in k and ``b != 0``, either raise
|
||
|
NonElementaryIntegralException, in which case the equation Dq + b*q == c
|
||
|
has no solution of degree at most n in k[t], or a solution q in k[t] of
|
||
|
this equation with deg(q) <= n.
|
||
|
"""
|
||
|
from .prde import parametric_log_deriv
|
||
|
eta = DE.d.quo(Poly(DE.t, DE.t)).as_expr()
|
||
|
|
||
|
with DecrementLevel(DE):
|
||
|
etaa, etad = frac_in(eta, DE.t)
|
||
|
ba, bd = frac_in(b, DE.t)
|
||
|
A = parametric_log_deriv(ba, bd, etaa, etad, DE)
|
||
|
if A is not None:
|
||
|
a, m, z = A
|
||
|
if a == 1:
|
||
|
raise NotImplementedError("is_deriv_in_field() is required to "
|
||
|
"solve this problem.")
|
||
|
# if c*z*t**m == Dp for p in k<t> and q = p/(z*t**m) in k[t] and
|
||
|
# deg(q) <= n:
|
||
|
# return q
|
||
|
# else:
|
||
|
# raise NonElementaryIntegralException
|
||
|
|
||
|
if c.is_zero:
|
||
|
return c # return 0
|
||
|
|
||
|
if n < c.degree(DE.t):
|
||
|
raise NonElementaryIntegralException
|
||
|
|
||
|
q = Poly(0, DE.t)
|
||
|
while not c.is_zero:
|
||
|
m = c.degree(DE.t)
|
||
|
if n < m:
|
||
|
raise NonElementaryIntegralException
|
||
|
# a1 = b + m*Dt/t
|
||
|
a1 = b.as_expr()
|
||
|
with DecrementLevel(DE):
|
||
|
# TODO: Write a dummy function that does this idiom
|
||
|
a1a, a1d = frac_in(a1, DE.t)
|
||
|
a1a = a1a*etad + etaa*a1d*Poly(m, DE.t)
|
||
|
a1d = a1d*etad
|
||
|
|
||
|
a2a, a2d = frac_in(c.LC(), DE.t)
|
||
|
|
||
|
sa, sd = rischDE(a1a, a1d, a2a, a2d, DE)
|
||
|
stm = Poly(sa.as_expr()/sd.as_expr()*DE.t**m, DE.t, expand=False)
|
||
|
q += stm
|
||
|
n = m - 1
|
||
|
c -= b*stm + derivation(stm, DE) # deg(c) becomes smaller
|
||
|
return q
|
||
|
|
||
|
|
||
|
def solve_poly_rde(b, cQ, n, DE, parametric=False):
|
||
|
"""
|
||
|
Solve a Polynomial Risch Differential Equation with degree bound ``n``.
|
||
|
|
||
|
This constitutes step 4 of the outline given in the rde.py docstring.
|
||
|
|
||
|
For parametric=False, cQ is c, a Poly; for parametric=True, cQ is Q ==
|
||
|
[q1, ..., qm], a list of Polys.
|
||
|
"""
|
||
|
# No cancellation
|
||
|
if not b.is_zero and (DE.case == 'base' or
|
||
|
b.degree(DE.t) > max(0, DE.d.degree(DE.t) - 1)):
|
||
|
|
||
|
if parametric:
|
||
|
# Delayed imports
|
||
|
from .prde import prde_no_cancel_b_large
|
||
|
return prde_no_cancel_b_large(b, cQ, n, DE)
|
||
|
return no_cancel_b_large(b, cQ, n, DE)
|
||
|
|
||
|
elif (b.is_zero or b.degree(DE.t) < DE.d.degree(DE.t) - 1) and \
|
||
|
(DE.case == 'base' or DE.d.degree(DE.t) >= 2):
|
||
|
|
||
|
if parametric:
|
||
|
from .prde import prde_no_cancel_b_small
|
||
|
return prde_no_cancel_b_small(b, cQ, n, DE)
|
||
|
|
||
|
R = no_cancel_b_small(b, cQ, n, DE)
|
||
|
|
||
|
if isinstance(R, Poly):
|
||
|
return R
|
||
|
else:
|
||
|
# XXX: Might k be a field? (pg. 209)
|
||
|
h, b0, c0 = R
|
||
|
with DecrementLevel(DE):
|
||
|
b0, c0 = b0.as_poly(DE.t), c0.as_poly(DE.t)
|
||
|
if b0 is None: # See above comment
|
||
|
raise ValueError("b0 should be a non-Null value")
|
||
|
if c0 is None:
|
||
|
raise ValueError("c0 should be a non-Null value")
|
||
|
y = solve_poly_rde(b0, c0, n, DE).as_poly(DE.t)
|
||
|
return h + y
|
||
|
|
||
|
elif DE.d.degree(DE.t) >= 2 and b.degree(DE.t) == DE.d.degree(DE.t) - 1 and \
|
||
|
n > -b.as_poly(DE.t).LC()/DE.d.as_poly(DE.t).LC():
|
||
|
|
||
|
# TODO: Is this check necessary, and if so, what should it do if it fails?
|
||
|
# b comes from the first element returned from spde()
|
||
|
if not b.as_poly(DE.t).LC().is_number:
|
||
|
raise TypeError("Result should be a number")
|
||
|
|
||
|
if parametric:
|
||
|
raise NotImplementedError("prde_no_cancel_b_equal() is not yet "
|
||
|
"implemented.")
|
||
|
|
||
|
R = no_cancel_equal(b, cQ, n, DE)
|
||
|
|
||
|
if isinstance(R, Poly):
|
||
|
return R
|
||
|
else:
|
||
|
h, m, C = R
|
||
|
# XXX: Or should it be rischDE()?
|
||
|
y = solve_poly_rde(b, C, m, DE)
|
||
|
return h + y
|
||
|
|
||
|
else:
|
||
|
# Cancellation
|
||
|
if b.is_zero:
|
||
|
raise NotImplementedError("Remaining cases for Poly (P)RDE are "
|
||
|
"not yet implemented (is_deriv_in_field() required).")
|
||
|
else:
|
||
|
if DE.case == 'exp':
|
||
|
if parametric:
|
||
|
raise NotImplementedError("Parametric RDE cancellation "
|
||
|
"hyperexponential case is not yet implemented.")
|
||
|
return cancel_exp(b, cQ, n, DE)
|
||
|
|
||
|
elif DE.case == 'primitive':
|
||
|
if parametric:
|
||
|
raise NotImplementedError("Parametric RDE cancellation "
|
||
|
"primitive case is not yet implemented.")
|
||
|
return cancel_primitive(b, cQ, n, DE)
|
||
|
|
||
|
else:
|
||
|
raise NotImplementedError("Other Poly (P)RDE cancellation "
|
||
|
"cases are not yet implemented (%s)." % DE.case)
|
||
|
|
||
|
if parametric:
|
||
|
raise NotImplementedError("Remaining cases for Poly PRDE not yet "
|
||
|
"implemented.")
|
||
|
raise NotImplementedError("Remaining cases for Poly RDE not yet "
|
||
|
"implemented.")
|
||
|
|
||
|
|
||
|
def rischDE(fa, fd, ga, gd, DE):
|
||
|
"""
|
||
|
Solve a Risch Differential Equation: Dy + f*y == g.
|
||
|
|
||
|
Explanation
|
||
|
===========
|
||
|
|
||
|
See the outline in the docstring of rde.py for more information
|
||
|
about the procedure used. Either raise NonElementaryIntegralException, in
|
||
|
which case there is no solution y in the given differential field,
|
||
|
or return y in k(t) satisfying Dy + f*y == g, or raise
|
||
|
NotImplementedError, in which case, the algorithms necessary to
|
||
|
solve the given Risch Differential Equation have not yet been
|
||
|
implemented.
|
||
|
"""
|
||
|
_, (fa, fd) = weak_normalizer(fa, fd, DE)
|
||
|
a, (ba, bd), (ca, cd), hn = normal_denom(fa, fd, ga, gd, DE)
|
||
|
A, B, C, hs = special_denom(a, ba, bd, ca, cd, DE)
|
||
|
try:
|
||
|
# Until this is fully implemented, use oo. Note that this will almost
|
||
|
# certainly cause non-termination in spde() (unless A == 1), and
|
||
|
# *might* lead to non-termination in the next step for a nonelementary
|
||
|
# integral (I don't know for certain yet). Fortunately, spde() is
|
||
|
# currently written recursively, so this will just give
|
||
|
# RuntimeError: maximum recursion depth exceeded.
|
||
|
n = bound_degree(A, B, C, DE)
|
||
|
except NotImplementedError:
|
||
|
# Useful for debugging:
|
||
|
# import warnings
|
||
|
# warnings.warn("rischDE: Proceeding with n = oo; may cause "
|
||
|
# "non-termination.")
|
||
|
n = oo
|
||
|
|
||
|
B, C, m, alpha, beta = spde(A, B, C, n, DE)
|
||
|
if C.is_zero:
|
||
|
y = C
|
||
|
else:
|
||
|
y = solve_poly_rde(B, C, m, DE)
|
||
|
|
||
|
return (alpha*y + beta, hn*hs)
|