def splitter(p): for y in p.atoms(Basic.Symbol): if not isinstance(derivation(y), Basic.Zero): c, q = p.as_polynomial(y).as_primitive() q = q.as_basic() h = gcd(q, derivation(q), y) s = quo(h, gcd(q, q.diff(y), y), y) c_split = splitter(c) if s.as_polynomial(y).degree() == 0: return (c_split[0], q * c_split[1]) q_split = splitter(normal(q / s, *V)) return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1]) else: return (S.One, p)
def risch_norman(f, x, rewrite=False): """Computes indefinite integral using extended Risch-Norman algorithm, also known as parallel Risch. This is a simplified version of full recursive Risch algorithm. It is designed for integrating various classes of functions including transcendental elementary or special functions like Airy, Bessel, Whittaker and Lambert. The main difference between this algorithm and the recursive one is that rather than computing a tower of differential extensions in a recursive way, it handles all cases in one shot. That's why it is called parallel Risch algorithm. This makes it much faster than the original approach. Another benefit is that it doesn't require to rewrite expressions in terms of complex exponentials. Rather it uses tangents and so antiderivatives are being found in a more familliar form. Risch-Norman algorithm can also handle special functions very easily without any additional effort. Just differentiation method must be known for a given function. Note that this algorithm is not a decision procedure. If it computes an antiderivative for a given integral then it's a proof that such function exists. However when it fails then there still may exist an antiderivative and a fallback to recurrsive Risch algorithm would be necessary. The question if this algorithm can be made a full featured decision procedure still remains open. For more information on the implemented algorithm refer to: [1] K. Geddes, L.Stefanus, On the Risch-Norman Integration Method and its Implementation in Maple, Proceedings of ISSAC'89, ACM Press, 212-217. [2] J. H. Davenport, On the Parallel Risch Algorithm (I), Proceedings of EUROCAM'82, LNCS 144, Springer, 144-157. [3] J. H. Davenport, On the Parallel Risch Algorithm (III): Use of Tangents, SIGSAM Bulletin 16 (1982), 3-6. [4] J. H. Davenport, B. M. Trager, On the Parallel Risch Algorithm (II), ACM Transactions on Mathematical Software 11 (1985), 356-362. """ f = Basic.sympify(f) if not f.has(x): return f * x rewritables = { (sin, cos, cot) : tan, (sinh, cosh, coth) : tanh, } if rewrite: for candidates, rule in rewritables.iteritems(): f = f.rewrite(candidates, rule) else: for candidates in rewritables.iterkeys(): if f.has(*candidates): break else: rewrite = True terms = components(f) for g in set(terms): h = g.diff(x) if not isinstance(h, Basic.Zero): terms |= components(h) terms = [ g for g in terms if g.has(x) ] V, in_terms, out_terms = [], [], {} for i, term in enumerate(terms): V += [ Symbol('x%s' % i) ] N = term.count_ops(symbolic=False) in_terms += [ (N, term, V[-1]) ] out_terms[V[-1]] = term in_terms.sort(lambda u, v: int(v[0] - u[0])) def substitute(expr): for _, g, symbol in in_terms: expr = expr.subs(g, symbol) return expr diffs = [ substitute(g.diff(x)) for g in terms ] denoms = [ g.as_numer_denom()[1] for g in diffs ] denom = reduce(lambda p, q: lcm(p, q, V), denoms) numers = [ normal(denom * g, *V) for g in diffs ] def derivation(h): return Basic.Add(*[ d * h.diff(v) for d, v in zip(numers, V) ]) def deflation(p): for y in p.atoms(Basic.Symbol): if not isinstance(derivation(p), Basic.Zero): c, q = p.as_polynomial(y).as_primitive() return deflation(c) * gcd(q, q.diff(y)) else: return p def splitter(p): for y in p.atoms(Basic.Symbol): if not isinstance(derivation(y), Basic.Zero): c, q = p.as_polynomial(y).as_primitive() q = q.as_basic() h = gcd(q, derivation(q), y) s = quo(h, gcd(q, q.diff(y), y), y) c_split = splitter(c) if s.as_polynomial(y).degree() == 0: return (c_split[0], q * c_split[1]) q_split = splitter(normal(q / s, *V)) return (c_split[0]*q_split[0]*s, c_split[1]*q_split[1]) else: return (S.One, p) special = [] for term in terms: if isinstance(term, Basic.Function): if isinstance(term, Basic.tan): special += [ (1 + substitute(term)**2, False) ] elif isinstance(term.func, tanh): special += [ (1 + substitute(term), False), (1 - substitute(term), False) ] #elif isinstance(term.func, Basic.LambertW): # special += [ (substitute(term), True) ] ff = substitute(f) P, Q = ff.as_numer_denom() u_split = splitter(denom) v_split = splitter(Q) s = u_split[0] * Basic.Mul(*[ g for g, a in special if a ]) a, b, c = [ p.as_polynomial(*V).degree() for p in [s, P, Q] ] candidate_denom = s * v_split[0] * deflation(v_split[1]) monoms = monomials(V, 1 + a + max(b, c)) linear = False while True: coeffs, candidate, factors = [], S.Zero, set() for i, monomial in enumerate(monoms): coeffs += [ Symbol('A%s' % i, dummy=True) ] candidate += coeffs[-1] * monomial candidate /= candidate_denom polys = [ v_split[0], v_split[1], u_split[0]] + [ s[0] for s in special ] for irreducibles in [ factorization(p, linear) for p in polys ]: factors |= irreducibles for i, irreducible in enumerate(factors): if not isinstance(irreducible, Basic.Number): coeffs += [ Symbol('B%s' % i, dummy=True) ] candidate += coeffs[-1] * Basic.log(irreducible) h = together(ff - derivation(candidate) / denom) numerator = h.as_numer_denom()[0].expand() if not isinstance(numerator, Basic.Add): numerator = [numerator] collected = {} for term in numerator: coeff, depend = term.as_independent(*V) if depend in collected: collected[depend] += coeff else: collected[depend] = coeff solutions = solve(collected.values(), coeffs) if solutions is None: if linear: break else: linear = True else: break if solutions is not None: antideriv = candidate.subs_dict(solutions) for C in coeffs: if C not in solutions: antideriv = antideriv.subs(C, S.Zero) antideriv = simplify(antideriv.subs_dict(out_terms)).expand() if isinstance(antideriv, Basic.Add): return Basic.Add(*antideriv.as_coeff_factors()[1]) else: return antideriv else: if not rewrite: return risch_norman(f, x, rewrite=True) else: return None
def apart(f, z, domain=None, index=None): """Computes full partial fraction decomposition of a univariate rational function over the algebraic closure of its field of definition. Although only gcd operations over the initial field are required, the expansion is returned in a formal form with linear denominators. However it is possible to force expansion of the resulting formal summations, and so factorization over a specified domain is performed. To specify the desired behavior of the algorithm use the 'domain' keyword. Setting it to None, which is done be default, will result in no factorization at all. Otherwise it can be assigned with one of Z, Q, R, C domain specifiers and the formal partial fraction expansion will be rewritten using all possible roots over this domain. If the resulting expansion contains formal summations, then for all those a single dummy index variable named 'a' will be generated. To change this default behavior issue new name via 'index' keyword. For more information on the implemented algorithm refer to: [1] M. Bronstein, B. Salvy, Full partial fraction decomposition of rational functions, in: M. Bronstein, ed., Proceedings ISSAC '93, ACM Press, Kiev, Ukraine, 1993, pp. 157-160. """ f = Basic.sympify(f) if isinstance(f, Basic.Add): return Add(*[ apart(g) for g in f ]) else: if f.is_fraction(z): f = normal(f, z) else: return f P, Q = f.as_numer_denom() if not Q.has(z): return f u = Function('u')(z) if index is None: A = Symbol('a', dummy=True) else: A = Symbol(index) partial, r = div(P, Q, z) f, q, U = r / Q, Q, [] for k, d in enumerate(sqf(q, z)): n, d = k + 1, d.as_basic() U += [ u.diff(z, k) ] h = normal(f * d**n, z) / u**n H, subs = [h], [] for j in range(1, n): H += [ H[-1].diff(z) / j ] for j in range(1, n+1): subs += [ (U[j-1], d.diff(z, j) / j) ] for j in range(0, n): P, Q = together(H[j]).as_numer_denom() for i in range(0, j+1): P = P.subs(*subs[j-i]) Q = Q.subs(*subs[0]) G = gcd(P, d, z) D = quo(d, G, z) g, B, _ = ext_gcd(Q, D, z) b = rem(P * B / g, D, z) term = b.subs(z, A) / (z - A)**(n-j) if domain is None: a = D.diff(z) if not a.has(z): partial += term.subs(A, -D.subs(z, 0) / a) else: partial += Basic.Sum(term, (A, Basic.RootOf(D, z))) else: raise NotImplementedError return partial