def test_splitfactor(): p = Poly(4 * x**4 * t**5 + (-4 * x**3 - 4 * x**4) * t**4 + (-3 * x**2 + 2 * x**3) * t**3 + (2 * x + 7 * x**2 + 2 * x**3) * t**2 + (1 - 4 * x - 4 * x**2) * t - 1 + 2 * x, t, field=True) DE = DifferentialExtension(extension={ 'D': [Poly(1, x), Poly(-t**2 - 3 / (2 * x) * t + 1 / (2 * x), t)] }) assert splitfactor(p, DE) == (Poly( 4 * x**4 * t**3 + (-8 * x**3 - 4 * x**4) * t**2 + (4 * x**2 + 8 * x**3) * t - 4 * x**2, t), Poly(t**2 + 1 / x * t + (1 - 2 * x) / (4 * x**2), t, domain='ZZ(x)')) assert splitfactor(Poly(x, t), DE) == (Poly(x, t), Poly(1, t)) r = Poly( -4 * x**4 * z**2 + 4 * x**6 * z**2 - z * x**3 - 4 * x**5 * z**3 + 4 * x**3 * z**3 + x**4 + z * x**5 - x**6, t) DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1 / x, t)]}) assert splitfactor(r, DE, coefficientD=True) == \ (Poly(x*z - x**2 - z*x**3 + x**4, t), Poly(-x**2 + 4*x**2*z**2, t)) assert splitfactor_sqf(r, DE, coefficientD=True) == \ (((Poly(x*z - x**2 - z*x**3 + x**4, t), 1),), ((Poly(-x**2 + 4*x**2*z**2, t), 1),)) assert splitfactor(Poly(0, t), DE) == (Poly(0, t), Poly(1, t)) assert splitfactor_sqf(Poly(0, t), DE) == (((Poly(0, t), 1), ), ())
def prde_normal_denom(fa, fd, G, DE): """ Parametric Risch Differential Equation - Normal part of the denominator. Given a derivation D on k[t] and f, g1, ..., gm in k(t) with f weakly normalized with respect to t, return the tuple (a, b, G, h) such that a, h in k[t], b in k<t>, G = [g1, ..., gm] in k(t)^m, and for any solution c1, ..., cm in Const(k) and y in k(t) of Dy + f*y == Sum(ci*gi, (i, 1, m)), q == y*h in k<t> satisfies a*Dq + b*q == Sum(ci*Gi, (i, 1, m)). """ dn, ds = splitfactor(fd, DE) Gas, Gds = list(zip(*G)) gd = reduce(lambda i, j: i.lcm(j), Gds, Poly(1, DE.t)) 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 ba = a * fa - dn * derivation(h, DE) * fd ba, bd = ba.cancel(fd, include=True) G = [(c * A).cancel(D, include=True) for A, D in G] return (a, (ba, bd), G, h)
def normal_denom(fa, fd, ga, gd, DE): """ Normal part of the denominator. 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 prde_normal_denom(fa, fd, G, DE): """ Parametric Risch Differential Equation - Normal part of the denominator. Given a derivation D on k[t] and f, g1, ..., gm in k(t) with f weakly normalized with respect to t, return the tuple (a, b, G, h) such that a, h in k[t], b in k<t>, G = [g1, ..., gm] in k(t)^m, and for any solution c1, ..., cm in Const(k) and y in k(t) of Dy + f*y == Sum(ci*gi, (i, 1, m)), q == y*h in k<t> satisfies a*Dq + b*q == Sum(ci*Gi, (i, 1, m)). """ dn, ds = splitfactor(fd, DE) Gas, Gds = zip(*G) gd = reduce(lambda i, j: i.lcm(j), Gds, Poly(1, DE.t)) 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 ba = a*fa - dn*derivation(h, DE)*fd ba, bd = ba.cancel(fd, include=True) G = [(c*A).cancel(D, include=True) for A, D in G] return (a, (ba, bd), G, h)
def normal_denom(fa, fd, ga, gd, DE): """ Normal part of the denominator. 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 test_splitfactor(): p = Poly(4*x**4*t**5 + (-4*x**3 - 4*x**4)*t**4 + (-3*x**2 + 2*x**3)*t**3 + (2*x + 7*x**2 + 2*x**3)*t**2 + (1 - 4*x - 4*x**2)*t - 1 + 2*x, t, field=True) DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(-t**2 - 3/(2*x)*t + 1/(2*x), t)]}) assert splitfactor(p, DE) == (Poly(4*x**4*t**3 + (-8*x**3 - 4*x**4)*t**2 + (4*x**2 + 8*x**3)*t - 4*x**2, t), Poly(t**2 + 1/x*t + (1 - 2*x)/(4*x**2), t, domain='ZZ(x)')) assert splitfactor(Poly(x, t), DE) == (Poly(x, t), Poly(1, t)) r = Poly(-4*x**4*z**2 + 4*x**6*z**2 - z*x**3 - 4*x**5*z**3 + 4*x**3*z**3 + x**4 + z*x**5 - x**6, t) DE = DifferentialExtension(extension={'D': [Poly(1, x), Poly(1/x, t)]}) assert splitfactor(r, DE, coefficientD=True) == \ (Poly(x*z - x**2 - z*x**3 + x**4, t), Poly(-x**2 + 4*x**2*z**2, t)) assert splitfactor_sqf(r, DE, coefficientD=True) == \ (((Poly(x*z - x**2 - z*x**3 + x**4, t), 1),), ((Poly(-x**2 + 4*x**2*z**2, t), 1),)) assert splitfactor(Poly(0, t), DE) == (Poly(0, t), Poly(1, t)) assert splitfactor_sqf(Poly(0, t), DE) == (((Poly(0, t), 1),), ())
def limited_integrate_reduce(fa, fd, G, DE): """ Simpler version of step 1 & 2 for the limited integration problem. Given a derivation D on k(t) and f, g1, ..., gn in k(t), return (a, b, h, N, g, V) such that a, b, h in k[t], N is a non-negative integer, g in k(t), V == [v1, ..., vm] in k(t)^m, and for any solution v in k(t), c1, ..., cm in C of f == Dv + Sum(ci*wi, (i, 1, m)), p = v*h is in k<t>, and p and the ci satisfy a*Dp + b*p == g + Sum(ci*vi, (i, 1, m)). Furthermore, if S1irr == Sirr, then p is in k[t], and if t is nonlinear or Liouvillian over k, then deg(p) <= N. So that the special part is always computed, this function calls the more general prde_special_denom() automatically if it cannot determine that S1irr == Sirr. Furthermore, it will automatically call bound_degree() when t is linear and non-Liouvillian, which for the transcendental case, implies that Dt == a*t + b with for some a, b in k*. """ dn, ds = splitfactor(fd, DE) E = [splitfactor(gd, DE) for _, gd in G] En, Es = list(zip(*E)) c = reduce(lambda i, j: i.lcm(j), (dn, ) + En) # lcm(dn, en1, ..., enm) hn = c.gcd(c.diff(DE.t)) a = hn b = -derivation(hn, DE) N = 0 # These are the cases where we know that S1irr = Sirr, but there could be # others, and this algorithm will need to be extended to handle them. if DE.case in ['base', 'primitive', 'exp', 'tan']: hs = reduce(lambda i, j: i.lcm(j), (ds, ) + Es) # lcm(ds, es1, ..., esm) a = hn * hs b = -derivation(hn, DE) - (hn * derivation(hs, DE)).quo(hs) mu = min(order_at_oo(fa, fd, DE.t), min([order_at_oo(ga, gd, DE.t) for ga, gd in G])) # So far, all the above are also nonlinear or Liouvillian, but if this # changes, then this will need to be updated to call bound_degree() # as per the docstring of this function (DE.case == 'other_linear'). N = hn.degree(DE.t) + hs.degree(DE.t) + max(0, 1 - DE.d.degree(DE.t) - mu) else: # TODO: implement this raise NotImplementedError V = [(-a * hn * ga).cancel(gd, include=True) for ga, gd in G] return (a, b, a, N, (a * hn * fa).cancel(fd, include=True), V)
def limited_integrate_reduce(fa, fd, G, DE): """ Simpler version of step 1 & 2 for the limited integration problem. Given a derivation D on k(t) and f, g1, ..., gn in k(t), return (a, b, h, N, g, V) such that a, b, h in k[t], N is a non-negative integer, g in k(t), V == [v1, ..., vm] in k(t)^m, and for any solution v in k(t), c1, ..., cm in C of f == Dv + Sum(ci*wi, (i, 1, m)), p = v*h is in k<t>, and p and the ci satisfy a*Dp + b*p == g + Sum(ci*vi, (i, 1, m)). Furthermore, if S1irr == Sirr, then p is in k[t], and if t is nonlinear or Liouvillian over k, then deg(p) <= N. So that the special part is always computed, this function calls the more general prde_special_denom() automatically if it cannot determine that S1irr == Sirr. Furthermore, it will automatically call bound_degree() when t is linear and non-Liouvillian, which for the transcendental case, implies that Dt == a*t + b with for some a, b in k*. """ dn, ds = splitfactor(fd, DE) E = [splitfactor(gd, DE) for _, gd in G] En, Es = zip(*E) c = reduce(lambda i, j: i.lcm(j), (dn,) + En) # lcm(dn, en1, ..., enm) hn = c.gcd(c.diff(DE.t)) a = hn b = -derivation(hn, DE) N = 0 # These are the cases where we know that S1irr = Sirr, but there could be # others, and this algorithm will need to be extended to handle them. if DE.case in ['base', 'primitive', 'exp', 'tan']: hs = reduce(lambda i, j: i.lcm(j), (ds,) + Es) # lcm(ds, es1, ..., esm) a = hn*hs b = -derivation(hn, DE) - (hn*derivation(hs, DE)).quo(hs) mu = min(order_at_oo(fa, fd, DE.t), min([order_at_oo(ga, gd, DE.t) for ga, gd in G])) # So far, all the above are also nonlinear or Liouvillian, but if this # changes, then this will need to be updated to call bound_degree() # as per the docstring of this function (DE.case == 'other_linear'). N = hn.degree(DE.t) + hs.degree(DE.t) + max(0, 1 - DE.d.degree(DE.t) - mu) else: # TODO: implement this raise NotImplementedError V = [(-a*hn*ga).cancel(gd, include=True) for ga, gd in G] return (a, b, a, N, (a*hn*fa).cancel(fd, include=True), V)
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 weak_normalizer(a, d, DE, z=None): """ Weak normalization. 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.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 parametric_log_deriv_heu(fa, fd, wa, wd, DE, c1=None): """ Parametric logarithmic derivative heuristic. Given a derivation D on k[t], f in k(t), and a hyperexponential monomial theta over k(t), raises either NotImplementedError, in which case the heuristic failed, or returns None, in which case it has proven that no solution exists, or returns a solution (n, m, v) of the equation n*f == Dv/v + m*Dtheta/theta, with v in k(t)* and n, m in ZZ with n != 0. If this heuristic fails, the structure theorem approach will need to be used. The argument w == Dtheta/theta """ # TODO: finish writing this and write tests c1 = c1 or Dummy('c1') p, a = fa.div(fd) q, b = wa.div(wd) B = max(0, derivation(DE.t, DE).degree(DE.t) - 1) C = max(p.degree(DE.t), q.degree(DE.t)) if q.degree(DE.t) > B: eqs = [p.nth(i) - c1 * q.nth(i) for i in range(B + 1, C + 1)] s = solve(eqs, c1) if not s or not s[c1].is_Rational: # deg(q) > B, no solution for c. return None N, M = s[c1].as_numer_denom() # N and M are integers N, M = Poly(N, DE.t), Poly(M, DE.t) nfmwa = N * fa * wd - M * wa * fd nfmwd = fd * wd Qv = is_log_deriv_k_t_radical_in_field(N * fa * wd - M * wa * fd, fd * wd, DE, 'auto') if Qv is None: # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. return None Q, e, v = Qv if e != 1: return None if Q.is_zero or v.is_zero: # Q == 0 or v == 0. return None return (Q * N, Q * M, v) if p.degree(DE.t) > B: return None c = lcm(fd.as_poly(DE.t).LC(), wd.as_poly(DE.t).LC()) l = fd.monic().lcm(wd.monic()) * Poly(c, DE.t) ln, ls = splitfactor(l, DE) z = ls * ln.gcd(ln.diff(DE.t)) if not z.has(DE.t): raise NotImplementedError("parametric_log_deriv_heu() " "heuristic failed: z in k.") u1, r1 = (fa * l.quo(fd)).div(z) # (l*f).div(z) u2, r2 = (wa * l.quo(wd)).div(z) # (l*w).div(z) eqs = [r1.nth(i) - c1 * r2.nth(i) for i in range(z.degree(DE.t))] s = solve(eqs, c1) if not s or not s[c1].is_Rational: # deg(q) <= B, no solution for c. return None M, N = s[c1].as_numer_denom() nfmwa = N.as_poly(DE.t) * fa * wd - M.as_poly(DE.t) * wa * fd nfmwd = fd * wd Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE) if Qv is None: # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. return None Q, v = Qv if Q.is_zero or v.is_zero: # Q == 0 or v == 0. return None return (Q * N, Q * M, v)
""" Checks if f can be written as the logarithmic derivative of a k(t)-radical. f in k(t) can be written as the logarithmic derivative of a k(t) radical if there exist n in ZZ and u in k(t) with n, u != 0 such that n*f == Du/u. Either returns (n, u) or None, which means that f cannot be written as the logarithmic derivative of a k(t)-radical. case is one of {'primitive', 'exp', 'tan', 'auto'} for the primitive, hyperexponential, and hypertangent cases, respectively. If case is 'auto', it will attempt to determine the type of the derivation automatically. """ fa, fd = fa.cancel(fd, include=True) # f must be simple n, s = splitfactor(fd, DE) if not s.is_one: pass #return None z = z or Dummy('z') H, b = residue_reduce(fa, fd, DE, z=z) if not b: # I will have to verify, but I believe that the answer should be # None in this case. This should never happen for the # functions given when solving the parametric logarithmic # derivative problem when integration elementary functions (see # Bronstein's book, page 255), so most likely this indicates a bug. return None roots = [(i, i.real_roots()) for i, _ in H]
def is_log_deriv_k_t_radical_in_field(fa, fd, DE, case='auto', z=None): """ Checks if f can be written as the logarithmic derivative of a k(t)-radical. f in k(t) can be written as the logarithmic derivative of a k(t) radical if there exist n in ZZ and u in k(t) with n, u != 0 such that n*f == Du/u. Either returns (n, u) or None, which means that f cannot be written as the logarithmic derivative of a k(t)-radical. case is one of {'primitive', 'exp', 'tan', 'auto'} for the primitive, hyperexponential, and hypertangent cases, respectively. If case it 'auto', it will attempt to determine the type of the derivation automatically. """ fa, fd = fa.cancel(fd, include=True) # f must be simple n, s = splitfactor(fd, DE) if not s.is_one: pass #return None z = z or Dummy('z') H, b = residue_reduce(fa, fd, DE, z=z) if not b: # I will have to verify, but I believe that the answer should be # None in this case. This should never happen for the # functions given when solving the parametric logarithmic # derivative problem when integration elementary functions (see # Bronstein's book, page 255), so most likely this indicates a bug. return None roots = [(i, i.real_roots()) for i, _ in H] if not all(len(j) == i.degree() and all(k.is_Rational for k in j) for i, j in roots): # If f is the logarithmic derivative of a k(t)-radical, then all the # roots of the resultant must be rational numbers. return None # [(a, i), ...], where i*log(a) is a term in the log-part of the integral # of f respolys, residues = zip(*roots) or [[], []] # Note: this might be empty, but everything below should work find in that # case (it should be the same as if it were [[1, 1]]) residueterms = [(H[j][1].subs(z, i), i) for j in xrange(len(H)) for i in residues[j]] # TODO: finish writing this and write tests p = cancel(fa.as_expr()/fd.as_expr() - residue_reduce_derivation(H, DE, z)) p = p.as_poly(DE.t) if p is None: # f - Dg will be in k[t] if f is the logarithmic derivative of a k(t)-radical return None if p.degree(DE.t) >= max(1, DE.d.degree(DE.t)): return None if case == 'auto': case = DE.case if case == 'exp': wa, wd = derivation(DE.t, DE).cancel(Poly(DE.t, DE.t), include=True) with DecrementLevel(DE): pa, pd = frac_in(p, DE.t, cancel=True) wa, wd = frac_in((wa, wd), DE.t) A = parametric_log_deriv(pa, pd, wa, wd, DE) if A is None: return None n, e, u = A u *= DE.t**e # raise NotImplementedError("The hyperexponential case is " # "not yet completely implemented for is_log_deriv_k_t_radical_in_field().") elif case == 'primitive': with DecrementLevel(DE): pa, pd = frac_in(p, DE.t) A = is_log_deriv_k_t_radical_in_field(pa, pd, DE, case='auto') if A is None: return None n, u = A elif case == 'base': # TODO: we can use more efficient residue reduction from ratint() if not fd.is_sqf or fa.degree() >= fd.degree(): # f is the logarithmic derivative in the base case if and only if # f = fa/fd, fd is square-free, deg(fa) < deg(fd), and # gcd(fa, fd) == 1. The last condition is handled by cancel() above. return None # Note: if residueterms = [], returns (1, 1) # f had better be 0 in that case. n = reduce(ilcm, [i.as_numer_denom()[1] for _, i in residueterms], S(1)) u = Mul(*[Pow(i, j*n) for i, j in residueterms]) return (n, u) elif case == 'tan': raise NotImplementedError("The hypertangent case is " "not yet implemented for is_log_deriv_k_t_radical_in_field()") elif case in ['other_linear', 'other_nonlinear']: # XXX: If these are supported by the structure theorems, change to NotImplementedError. raise ValueError("The %s case is not supported in this function." % case) else: raise ValueError("case must be one of {'primitive', 'exp', 'tan', " "'base', 'auto'}, not %s" % case) common_denom = reduce(ilcm, [i.as_numer_denom()[1] for i in [j for _, j in residueterms]] + [n], S(1)) residueterms = [(i, j*common_denom) for i, j in residueterms] m = common_denom//n assert common_denom == n*m # Verify exact division u = cancel(u**m*Mul(*[Pow(i, j) for i, j in residueterms])) return (common_denom, u)
def parametric_log_deriv_heu(fa, fd, wa, wd, DE, c1=None): """ Parametric logarithmic derivative heuristic. Given a derivation D on k[t], f in k(t), and a hyperexponential monomial theta over k(t), raises either NotImplementedError, in which case the heuristic failed, or returns None, in which case it has proven that no solution exists, or returns a solution (n, m, v) of the equation n*f == Dv/v + m*Dtheta/theta, with v in k(t)* and n, m in ZZ with n != 0. If this heuristic fails, the structure theorem approach will need to be used. The argument w == Dtheta/theta """ # TODO: finish writing this and write tests c1 = c1 or Dummy('c1') p, a = fa.div(fd) q, b = wa.div(wd) B = max(0, derivation(DE.t, DE).degree(DE.t) - 1) C = max(p.degree(DE.t), q.degree(DE.t)) if q.degree(DE.t) > B: eqs = [p.nth(i) - c1*q.nth(i) for i in range(B + 1, C + 1)] s = solve(eqs, c1) if not s or not s[c1].is_Rational: # deg(q) > B, no solution for c. return None N, M = s[c1].as_numer_denom() # N and M are integers N, M = Poly(N, DE.t), Poly(M, DE.t) nfmwa = N*fa*wd - M*wa*fd nfmwd = fd*wd Qv = is_log_deriv_k_t_radical_in_field(N*fa*wd - M*wa*fd, fd*wd, DE, 'auto') if Qv is None: # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. return None Q, e, v = Qv if e != 1: return None if Q.is_zero or v.is_zero: # Q == 0 or v == 0. return None return (Q*N, Q*M, v) if p.degree(DE.t) > B: return None c = lcm(fd.as_poly(DE.t).LC(), wd.as_poly(DE.t).LC()) l = fd.monic().lcm(wd.monic())*Poly(c, DE.t) ln, ls = splitfactor(l, DE) z = ls*ln.gcd(ln.diff(DE.t)) if not z.has(DE.t): raise NotImplementedError("parametric_log_deriv_heu() " "heuristic failed: z in k.") u1, r1 = (fa*l.quo(fd)).div(z) # (l*f).div(z) u2, r2 = (wa*l.quo(wd)).div(z) # (l*w).div(z) eqs = [r1.nth(i) - c1*r2.nth(i) for i in range(z.degree(DE.t))] s = solve(eqs, c1) if not s or not s[c1].is_Rational: # deg(q) <= B, no solution for c. return None M, N = s[c1].as_numer_denom() nfmwa = N.as_poly(DE.t)*fa*wd - M.as_poly(DE.t)*wa*fd nfmwd = fd*wd Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE) if Qv is None: # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical. return None Q, v = Qv if Q.is_zero or v.is_zero: # Q == 0 or v == 0. return None return (Q*N, Q*M, v)