def test_exclude(): x, y, a = map(Symbol, 'xya') p = Wild('p', exclude=[1, x]) q = Wild('q') r = Wild('r', exclude=[sin, y]) assert sin(x).match(r) is None assert cos(y).match(r) is None e = 3*x**2 + y*x + a assert e.match(p*x**2 + q*x + r) == {p: 3, q: y, r: a} e = x + 1 assert e.match(x + p) is None assert e.match(p + 1) is None assert e.match(x + 1 + p) == {p: 0} e = cos(x) + 5*sin(y) assert e.match(r) is None assert e.match(cos(y) + r) is None assert e.match(r + p*sin(q)) == {r: cos(x), p: 5, q: y}
def __new__(cls, recurrence, yn, n, initial=None, start=0): if not isinstance(yn, AppliedUndef): raise TypeError( "recurrence sequence must be an applied undefined function" ", found `{}`".format(yn)) if not isinstance(n, Basic) or not n.is_symbol: raise TypeError("recurrence variable must be a symbol" ", found `{}`".format(n)) if yn.args != (n, ): raise TypeError("recurrence sequence does not match symbol") y = yn.func k = Wild("k", exclude=(n, )) degree = 0 # Find all applications of y in the recurrence and check that: # 1. The function y is only being used with a single argument; and # 2. All arguments are n + k for constant negative integers k. prev_ys = recurrence.find(y) for prev_y in prev_ys: if len(prev_y.args) != 1: raise TypeError("Recurrence should be in a single variable") shift = prev_y.args[0].match(n + k)[k] if not (shift.is_constant() and shift.is_integer and shift < 0): raise TypeError("Recurrence should have constant," " negative, integer shifts" " (found {})".format(prev_y)) if -shift > degree: degree = -shift if not initial: initial = [Dummy("c_{}".format(k)) for k in range(degree)] if len(initial) != degree: raise ValueError("Number of initial terms must equal degree") degree = Integer(degree) start = sympify(start) initial = Tuple(*(sympify(x) for x in initial)) seq = Basic.__new__(cls, recurrence, yn, n, initial, start) seq.cache = {y(start + k): init for k, init in enumerate(initial)} seq.degree = degree return seq
def match_2nd_hypergeometric(eq, func): x = func.args[0] df = func.diff(x) a3 = Wild('a3', exclude=[func, func.diff(x), func.diff(x, 2)]) b3 = Wild('b3', exclude=[func, func.diff(x), func.diff(x, 2)]) c3 = Wild('c3', exclude=[func, func.diff(x), func.diff(x, 2)]) deq = a3*(func.diff(x, 2)) + b3*df + c3*func r = collect(eq, [func.diff(x, 2), func.diff(x), func]).match(deq) if r: if not all([r[key].is_polynomial() for key in r]): n, d = eq.as_numer_denom() eq = expand(n) r = collect(eq, [func.diff(x, 2), func.diff(x), func]).match(deq) if r and r[a3]!=0: A = cancel(r[b3]/r[a3]) B = cancel(r[c3]/r[a3]) return [A, B] else: return []
def _equation(self, fx, x, order): P, Q = self.wilds() df = fx.diff(x) eq = self.ode_problem.eq_expanded c = Wild('c', exclude=[fx]) d = Wild('d', exclude=[df, fx.diff(x, 2)]) e = Wild('e', exclude=[df]) eq = expand(eq) eq = eq.collect(fx) r = eq.match(e * df + d) self.fxx = None self.gx = None self.kx = None if r: r2 = r.copy() r2[c] = S.Zero if r2[d].is_Add: # Separate the terms having f(x) to r[d] and # remaining to r[c] no_f, r2[d] = r2[d].as_independent(fx) r2[c] += no_f factor = simplify(r2[d].diff(fx) / r[e]) if factor and not factor.has(fx): r2[d] = factor_terms(r2[d]) u = r2[d].as_independent(fx, as_Add=False)[1] r2.update({'a': e, 'b': d, 'c': c, 'u': u}) r2[d] /= u r2[e] /= u.diff(fx) self.coeffs = r2 du = u.diff(x) temp = du + (r2[r2['b']] / r2[r2['a']]) * u - r2[r2['c']] / r2[r2['a']] temp = expand(temp / temp.coeff(fx.diff(x))) self.fxx = r2[e] self.kx = r2[d] self.gx = -r2[c] self.substituting = u return fx.diff(x) + P * fx - Q return S.Zero
def test_match_deriv_bug1(): n = Function('n') l = Function('l') x = Symbol('x') p = Wild('p') e = diff(l(x), x)/x - diff(diff(n(x), x), x)/2 - \ diff(n(x), x)**2/4 + diff(n(x), x)*diff(l(x), x)/4 e = e.subs(n(x), -l(x)).doit() t = x*exp(-l(x)) t2 = t.diff(x, x)/t assert e.match( (p*t2).expand() ) == {p: Rational(-1, 2)}
def test_core_symbol(): # make the Symbol a unique name that doesn't class with any other # testing variable in this file since after this test the symbol # having the same name will be cached as noncommutative for c in ( Dummy, Dummy("x", commutative=False), Symbol, Symbol("_issue_3130", commutative=False), Wild, Wild("x"), ): check(c)
def _compare_pretty(a, b): from sympy.series.order import Order if isinstance(a, Order) and not isinstance(b, Order): return 1 if not isinstance(a, Order) and isinstance(b, Order): return -1 if a.is_Rational and b.is_Rational: return cmp(a.p * b.q, b.p * a.q) else: from sympy.core.symbol import Wild p1, p2, p3 = Wild("p1"), Wild("p2"), Wild("p3") r_a = a.match(p1 * p2**p3) if r_a and p3 in r_a: a3 = r_a[p3] r_b = b.match(p1 * p2**p3) if r_b and p3 in r_b: b3 = r_b[p3] c = Basic.compare(a3, b3) if c != 0: return c return Basic.compare(a, b)
def test_gate(): """Test a basic gate.""" h = HadamardGate(1) assert h.min_qubits == 2 assert h.nqubits == 1 i0 = Wild('i0') i1 = Wild('i1') h0_w1 = HadamardGate(i0) h0_w2 = HadamardGate(i0) h1_w1 = HadamardGate(i1) assert h0_w1 == h0_w2 assert h0_w1 != h1_w1 assert h1_w1 != h0_w2 cnot_10_w1 = CNOT(i1, i0) cnot_10_w2 = CNOT(i1, i0) cnot_01_w1 = CNOT(i0, i1) assert cnot_10_w1 == cnot_10_w2 assert cnot_10_w1 != cnot_01_w1 assert cnot_10_w2 != cnot_01_w1
def hyper_re(DE, r, k): """Converts a DE into a RE. Performs the substitution: .. math:: x^l f^j(x) \\to (k + 1 - l)_j . a_{k + j - l} Normalises the terms so that lowest order of a term is always r(k). Examples ======== >>> from sympy import Function, Derivative >>> from sympy.series.formal import hyper_re >>> from sympy.abc import x, k >>> f, r = Function('f'), Function('r') >>> hyper_re(-f(x) + Derivative(f(x)), r, k) (k + 1)*r(k + 1) - r(k) >>> hyper_re(-x*f(x) + Derivative(f(x), (x, 2)), r, k) (k + 2)*(k + 3)*r(k + 3) - r(k) See Also ======== sympy.series.formal.exp_re """ RE = S.Zero g = DE.atoms(Function).pop() x = g.atoms(Symbol).pop() mini = None for t in Add.make_args(DE.expand()): coeff, d = t.as_independent(g) c, v = coeff.as_independent(x) l = v.as_coeff_exponent(x)[1] if isinstance(d, Derivative): j = d.derivative_count else: j = 0 RE += c * rf(k + 1 - l, j) * r(k + j - l) if mini is None or j - l < mini: mini = j - l RE = RE.subs(k, k - mini) m = Wild('m') return RE.collect(r(k + m))
def solve_ODE_first_order(eq, f): """ solves many kinds of first order odes, different methods are used depending on the form of the given equation. Now the linear and Bernoulli cases are implemented. """ from sympy.integrals.integrals import integrate C1 = Symbol("C1") x = f.args[0] f = f.func #linear case: a(x)*f'(x)+b(x)*f(x)+c(x) = 0 a = Wild('a', exclude=[f(x)]) b = Wild('b', exclude=[f(x)]) c = Wild('c', exclude=[f(x)]) r = eq.match(a * diff(f(x), x) + b * f(x) + c) if r: t = exp(integrate(r[b] / r[a], x)) tt = integrate(t * (-r[c] / r[a]), x) return (tt + C1) / t #Bernoulli case: a(x)*f'(x)+b(x)*f(x)+c(x)*f(x)^n = 0 n = Wild('n', exclude=[f(x)]) r = eq.match(a * diff(f(x), x) + b * f(x) + c * f(x)**n) if r: if r[n] == 1: return C1 * exp(integrate(-(r[b] + r[c]), x)) # r[n] != 1 ie, the real bernoulli case t = exp((1 - r[n]) * integrate(r[b] / r[a], x)) tt = (r[n] - 1) * integrate(t * r[c] / r[a], x) return ((tt + C1) / t)**(1 / (1 - r[n])) #other cases of first order odes will be implemented here raise NotImplementedError("solve_ODE_first_order: Cannot solve " + str(eq))
def solve_ODE_first_order(eq, f): """ solves many kinds of first order odes, different methods are used depending on the form of the given equation. Now the linear case is implemented. """ from sympy.integrals.integrals import integrate x = f.args[0] f = f.func #linear case: a(x)*f'(x)+b(x)*f(x)+c(x) = 0 a = Wild('a', exclude=[f(x)]) b = Wild('b', exclude=[f(x)]) c = Wild('c', exclude=[f(x)]) r = eq.match(a * diff(f(x), x) + b * f(x) + c) if r: t = C.exp(integrate(r[b] / r[a], x)) tt = integrate(t * (-r[c] / r[a]), x) return (tt + Symbol("C1")) / t #other cases of first order odes will be implemented here raise NotImplementedError("solve_ODE_first_order: Cannot solve " + str(eq))
def test_match_wild_wild(): p = Wild('p') q = Wild('q') r = Wild('r') assert p.match(q + r) in [ {q: p, r: 0}, {q: 0, r: p} ] assert p.match(q*r) in [ {q: p, r: 1}, {q: 1, r: p} ] p = Wild('p') q = Wild('q', exclude=[p]) r = Wild('r') assert p.match(q + r) == {q: 0, r: p} assert p.match(q*r) == {q: 1, r: p} p = Wild('p') q = Wild('q', exclude=[p]) r = Wild('r', exclude=[p]) assert p.match(q + r) is None assert p.match(q*r) is None
def limitinf(e, x, leadsimp=False): """Limit e(x) for x-> oo. Explanation =========== If ``leadsimp`` is True, an attempt is made to simplify the leading term of the series expansion of ``e``. That may succeed even if ``e`` cannot be simplified. """ # rewrite e in terms of tractable functions only if not e.has(x): return e # e is a constant from sympy.simplify.powsimp import powdenest if e.has(Order): e = e.expand().removeO() if not x.is_positive or x.is_integer: # We make sure that x.is_positive is True and x.is_integer is None # so we get all the correct mathematical behavior from the expression. # We need a fresh variable. p = Dummy('p', positive=True) e = e.subs(x, p) x = p e = e.rewrite('tractable', deep=True, limitvar=x) e = powdenest(e) c0, e0 = mrv_leadterm(e, x) sig = sign(e0, x) if sig == 1: return S.Zero # e0>0: lim f = 0 elif sig == -1: # e0<0: lim f = +-oo (the sign depends on the sign of c0) if c0.match(I * Wild("a", exclude=[I])): return c0 * oo s = sign(c0, x) # the leading term shouldn't be 0: if s == 0: raise ValueError("Leading term should not be 0") return s * oo elif sig == 0: if leadsimp: c0 = c0.simplify() return limitinf(c0, x, leadsimp) # e0=0: lim f = lim c0 else: raise ValueError("{} could not be evaluated".format(sig))
def telescopic(L, R, limits): ''' Tries to perform the summation using the telescopic property. Return None if not possible. ''' (i, a, b) = limits if L.is_Add or R.is_Add: return None # We want to solve(L.subs(i, i + m) + R, m) # First we try a simple match since this does things that # solve doesn't do, e.g. solve(f(k+m)-f(k), m) fails k = Wild("k") sol = (-R).match(L.subs(i, i + k)) s = None if sol and k in sol: s = sol[k] if not (s.is_Integer and L.subs(i, i + s) == -R): # sometimes match fail(f(x+2).match(-f(x+k))->{k: -2 - 2x})) s = None # But there are things that match doesn't do that solve # can do, e.g. determine that 1/(x + m) = 1/(1 - x) when m = 1 if s is None: m = Dummy('m') try: sol = solve(L.subs(i, i + m) + R, m) or [] except NotImplementedError: return None sol = [ si for si in sol if si.is_Integer and (L.subs(i, i + si) + R).expand().is_zero ] if len(sol) != 1: return None s = sol[0] if s < 0: return telescopic_direct(R, L, abs(s), (i, a, b)) elif s > 0: return telescopic_direct(L, R, s, (i, a, b))
def deriv_degree(expr, func): """ get the order of a given ode, the function is implemented recursively """ a = Wild('a', exclude=[func]) order = 0 if isinstance(expr, Derivative): order = len(expr.symbols) else: for arg in expr.args: if isinstance(arg, Derivative): order = max(order, len(arg.symbols)) elif expr.match(a): order = 0 else: for arg1 in arg.args: order = max(order, deriv_degree(arg1, func)) return order
def test_issue_3773(): x = symbols('x') z, phi, r = symbols('z phi r') c, A, B, N = symbols('c A B N', cls=Wild) l = Wild('l', exclude=(0,)) eq = z * sin(2*phi) * r**7 matcher = c * sin(phi*N)**l * r**A * log(r)**B assert eq.match(matcher) == {c: z, l: 1, N: 2, A: 7, B: 0} assert (-eq).match(matcher) == {c: -z, l: 1, N: 2, A: 7, B: 0} assert (x*eq).match(matcher) == {c: x*z, l: 1, N: 2, A: 7, B: 0} assert (-7*x*eq).match(matcher) == {c: -7*x*z, l: 1, N: 2, A: 7, B: 0} matcher = c*sin(phi*N)**l * r**A assert eq.match(matcher) == {c: z, l: 1, N: 2, A: 7} assert (-eq).match(matcher) == {c: -z, l: 1, N: 2, A: 7} assert (x*eq).match(matcher) == {c: x*z, l: 1, N: 2, A: 7} assert (-7*x*eq).match(matcher) == {c: -7*x*z, l: 1, N: 2, A: 7}
def _eval_nseries(self, x, n, logx, cdir=0): # NOTE Please see the comment at the beginning of this file, labelled # IMPORTANT. from sympy.functions.elementary.complexes import sign from sympy.functions.elementary.integers import ceiling from sympy.series.limits import limit from sympy.series.order import Order from sympy.simplify.powsimp import powsimp arg = self.exp arg_series = arg._eval_nseries(x, n=n, logx=logx) if arg_series.is_Order: return 1 + arg_series arg0 = limit(arg_series.removeO(), x, 0) if arg0 is S.NegativeInfinity: return Order(x**n, x) if arg0 is S.Infinity: return self # checking for indecisiveness/ sign terms in arg0 if any(isinstance(arg, (sign, ImaginaryUnit)) for arg in arg0.args): return self t = Dummy("t") nterms = n try: cf = Order(arg.as_leading_term(x, logx=logx), x).getn() except (NotImplementedError, PoleError): cf = 0 if cf and cf > 0: nterms = ceiling(n/cf) exp_series = exp(t)._taylor(t, nterms) r = exp(arg0)*exp_series.subs(t, arg_series - arg0) if cf and cf > 1: r += Order((arg_series - arg0)**n, x)/x**((cf-1)*n) else: r += Order((arg_series - arg0)**n, x) r = r.expand() r = powsimp(r, deep=True, combine='exp') # powsimp may introduce unexpanded (-1)**Rational; see PR #17201 simplerat = lambda x: x.is_Rational and x.q in [3, 4, 6] w = Wild('w', properties=[simplerat]) r = r.replace(S.NegativeOne**w, expand_complex(S.NegativeOne**w)) return r
def _transform_DE_RE(DE, g, k, order, syms): """Converts DE with free parameters into RE of hypergeometric type.""" from sympy.solvers.solveset import linsolve RE = hyper_re(DE, g, k) eq = [] for i in range(1, order): coeff = RE.coeff(g(k + i)) eq.append(coeff) sol = dict(zip(syms, (i for s in linsolve(eq, list(syms)) for i in s))) if sol: m = Wild('m') RE = RE.subs(sol) RE = RE.factor().as_numer_denom()[0].collect(g(k + m)) RE = RE.as_coeff_mul(g)[1][0] for i in range(order): # smallest order should be g(k) if RE.coeff(g(k + i)) and i: RE = RE.subs(k, k - i) break return RE
def ode_order(expr, func): """ Returns the order of a given differential equation with respect to func. This function is implemented recursively. Examples ======== >>> from sympy import Function >>> from sympy.solvers.deutils import ode_order >>> from sympy.abc import x >>> f, g = map(Function, ['f', 'g']) >>> ode_order(f(x).diff(x, 2) + f(x).diff(x)**2 + ... f(x).diff(x), f(x)) 2 >>> ode_order(f(x).diff(x, 2) + g(x).diff(x, 3), f(x)) 2 >>> ode_order(f(x).diff(x, 2) + g(x).diff(x, 3), g(x)) 3 """ a = Wild('a', exclude=[func]) if expr.match(a): return 0 if isinstance(expr, Derivative): if expr.args[0] == func: return len(expr.variables) else: order = 0 for arg in expr.args[0].args: order = max(order, ode_order(arg, func) + len(expr.variables)) return order else: order = 0 for arg in expr.args: order = max(order, ode_order(arg, func)) return order
def test_Wild_properties(): S = sympify # these tests only include Atoms x = Symbol("x") y = Symbol("y") p = Symbol("p", positive=True) k = Symbol("k", integer=True) n = Symbol("n", integer=True, positive=True) given_patterns = [ x, y, p, k, -k, n, -n, S(-3), S(3), pi, Rational(3, 2), I ] integerp = lambda k: k.is_integer positivep = lambda k: k.is_positive symbolp = lambda k: k.is_Symbol realp = lambda k: k.is_extended_real S = Wild("S", properties=[symbolp]) R = Wild("R", properties=[realp]) Y = Wild("Y", exclude=[x, p, k, n]) P = Wild("P", properties=[positivep]) K = Wild("K", properties=[integerp]) N = Wild("N", properties=[positivep, integerp]) given_wildcards = [S, R, Y, P, K, N] goodmatch = { S: (x, y, p, k, n), R: (p, k, -k, n, -n, -3, 3, pi, Rational(3, 2)), Y: (y, -3, 3, pi, Rational(3, 2), I), P: (p, n, 3, pi, Rational(3, 2)), K: (k, -k, n, -n, -3, 3), N: (n, 3) } for A in given_wildcards: for pat in given_patterns: d = pat.match(A) if pat in goodmatch[A]: assert d[A] in goodmatch[A] else: assert d is None
def _eval_nseries(self, x, n, logx): # NOTE Please see the comment at the beginning of this file, labelled # IMPORTANT. from sympy import limit, oo, Order, powsimp, Wild, expand_complex arg = self.args[0] arg_series = arg._eval_nseries(x, n=n, logx=logx) if arg_series.is_Order: return 1 + arg_series arg0 = limit(arg_series.removeO(), x, 0) if arg0 in [-oo, oo]: return self t = Dummy("t") exp_series = exp(t)._taylor(t, n) o = exp_series.getO() exp_series = exp_series.removeO() r = exp(arg0) * exp_series.subs(t, arg_series - arg0) r += Order(o.expr.subs(t, (arg_series - arg0)), x) r = r.expand() r = powsimp(r, deep=True, combine='exp') # powsimp may introduce unexpanded (-1)**Rational; see PR #17201 simplerat = lambda x: x.is_Rational and x.q in [3, 4, 6] w = Wild('w', properties=[simplerat]) r = r.replace((-1)**w, expand_complex((-1)**w)) return r
def _check_varsh_sum_872_4(e): alpha = symbols('alpha') beta = symbols('beta') a = Wild('a') b = Wild('b') c = Wild('c') cp = Wild('cp') gamma = Wild('gamma') gammap = Wild('gammap') cg1 = CG(a, alpha, b, beta, c, gamma) cg2 = CG(a, alpha, b, beta, cp, gammap) match1 = e.match(Sum(cg1 * cg2, (alpha, -a, a), (beta, -b, b))) if match1 is not None and len(match1) == 6: return (KroneckerDelta(c, cp) * KroneckerDelta(gamma, gammap)).subs(match1) match2 = e.match(Sum(cg1**2, (alpha, -a, a), (beta, -b, b))) if match2 is not None and len(match2) == 4: return S.One return e
def rsolve(f, y, init=None): """Solve univariate recurrence with rational coefficients. Given k-th order linear recurrence Ly = f, or equivalently: a_{k}(n) y(n+k) + a_{k-1}(n) y(n+k-1) + ... + a_{0}(n) y(n) = f where a_{i}(n), for i=0..k, are polynomials or rational functions in n, and f is a hypergeometric function or a sum of a fixed number of pairwise dissimilar hypergeometric terms in n, finds all solutions or returns None, if none were found. Initial conditions can be given as a dictionary in two forms: [1] { n_0 : v_0, n_1 : v_1, ..., n_m : v_m } [2] { y(n_0) : v_0, y(n_1) : v_1, ..., y(n_m) : v_m } or as a list L of values: L = [ v_0, v_1, ..., v_m ] where L[i] = v_i, for i=0..m, maps to y(n_i). As an example lets consider the following recurrence: (n - 1) y(n + 2) - (n**2 + 3 n - 2) y(n + 1) + 2 n (n + 1) y(n) == 0 >>> from sympy import Function, rsolve >>> from sympy.abc import n >>> y = Function('y') >>> f = (n-1)*y(n+2) - (n**2+3*n-2)*y(n+1) + 2*n*(n+1)*y(n) >>> rsolve(f, y(n)) C0*gamma(1 + n) + C1*2**n >>> rsolve(f, y(n), { y(0):0, y(1):3 }) -3*gamma(1 + n) + 3*2**n """ if isinstance(f, Equality): f = f.lhs - f.rhs if f.is_Add: F = f.args else: F = [f] k = Wild('k') n = y.args[0] h_part = {} i_part = S.Zero for g in F: if g.is_Mul: G = g.args else: G = [g] coeff = S.One kspec = None for h in G: if h.is_Function: if h.func == y.func: result = h.args[0].match(n + k) if result is not None: kspec = int(result[k]) else: raise ValueError("'%s(%s+k)' expected, got '%s'" % (y.func, n, h)) else: raise ValueError("'%s' expected, got '%s'" % (y.func, h.func)) else: coeff *= h if kspec is not None: if kspec in h_part: h_part[kspec] += coeff else: h_part[kspec] = coeff else: i_part += coeff for k, coeff in h_part.iteritems(): h_part[k] = simplify(coeff) common = S.One for coeff in h_part.itervalues(): if coeff.is_rational_function(n): if not coeff.is_polynomial(n): common = lcm(common, coeff.as_numer_denom()[1], n) else: raise ValueError("Polynomial or rational function expected, got '%s'" % coeff) i_numer, i_denom = i_part.as_numer_denom() if i_denom.is_polynomial(n): common = lcm(common, i_denom, n) if common is not S.One: for k, coeff in h_part.iteritems(): numer, denom = coeff.as_numer_denom() h_part[k] = numer*exquo(common, denom, n) i_part = i_numer*exquo(common, i_denom, n) K_min = min(h_part.keys()) if K_min < 0: K = abs(K_min) H_part = {} i_part = i_part.subs(n, n+K).expand() common = common.subs(n, n+K).expand() for k, coeff in h_part.iteritems(): H_part[k+K] = coeff.subs(n, n+K).expand() else: H_part = h_part K_max = max(H_part.keys()) coeffs = [] for i in xrange(0, K_max+1): if i in H_part: coeffs.append(H_part[i]) else: coeffs.append(S.Zero) result = rsolve_hyper(coeffs, i_part, n, symbols=True) if result is None: return None else: solution, symbols = result if symbols and init is not None: equations = [] if type(init) is list: for i in xrange(0, len(init)): eq = solution.subs(n, i) - init[i] equations.append(eq) else: for k, v in init.iteritems(): try: i = int(k) except TypeError: if k.is_Function and k.func == y.func: i = int(k.args[0]) else: raise ValueError("Integer or term expected, got '%s'" % k) eq = solution.subs(n, i) - v equations.append(eq) result = solve(equations, *symbols) if result is None: return None else: for k, v in result.iteritems(): solution = solution.subs(k, v) return (solution.expand()) / common
def _eval_nseries(self, x, n, logx, cdir=0): # NOTE Please see the comment at the beginning of this file, labelled # IMPORTANT. from sympy import im, cancel, I, Order, logcombine from itertools import product if not logx: logx = log(x) if self.args[0] == x: return logx arg = self.args[0] k, l = Wild("k"), Wild("l") r = arg.match(k * x**l) if r is not None: k, l = r[k], r[l] if l != 0 and not l.has(x) and not k.has(x): r = log(k) + l * logx # XXX true regardless of assumptions? return r def coeff_exp(term, x): coeff, exp = S.One, S.Zero for factor in Mul.make_args(term): if factor.has(x): base, exp = factor.as_base_exp() if base != x: try: return term.leadterm(x) except ValueError: return term, S.Zero else: coeff *= factor return coeff, exp # TODO new and probably slow try: a, b = arg.leadterm(x) s = arg.nseries(x, n=n + b, logx=logx) except (ValueError, NotImplementedError): s = arg.nseries(x, n=n, logx=logx) while s.is_Order: n += 1 s = arg.nseries(x, n=n, logx=logx) a, b = s.removeO().leadterm(x) p = cancel(s / (a * x**b) - 1).expand().powsimp() if p.has(exp): p = logcombine(p) if isinstance(p, Order): n = p.getn() _, d = coeff_exp(p, x) if not d.is_positive: return log(a) + b * logx + Order(x**n, x) def mul(d1, d2): res = {} for e1, e2 in product(d1, d2): ex = e1 + e2 if ex < n: res[ex] = res.get(ex, S.Zero) + d1[e1] * d2[e2] return res pterms = {} for term in Add.make_args(p): co1, e1 = coeff_exp(term, x) pterms[e1] = pterms.get(e1, S.Zero) + co1.removeO() k = S.One terms = {} pk = pterms while k * d < n: coeff = -(-1)**k / k for ex in pk: terms[ex] = terms.get(ex, S.Zero) + coeff * pk[ex] pk = mul(pk, pterms) k += S.One res = log(a) + b * logx for ex in terms: res += terms[ex] * x**(ex) if cdir != 0: cdir = self.args[0].dir(x, cdir) if a.is_real and a.is_negative and im(cdir) < 0: res -= 2 * I * S.Pi return res + Order(x**n, x)
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] http://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) # There are some functions that Heurisch cannot currently handle, # so do not even try. # Set _try_heurisch=True to skip this check 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) 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): # using copy of 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])) ) #terms.add( x*(li(M[a]*x**M[b]) - (x**M[b])**(-1/M[b])*Ei((M[b]+1)*log(M[a]*x**M[b])/M[b])) ) #terms.add( x*(li(M[a]*x**M[b]) - x*Ei((M[b]+1)*log(M[a]*x**M[b])/M[b])) ) #terms.add( li(M[a]*x**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: # M[a].is_negative or unknown 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: terms.add(acosh(sqrt(M[a]/M[b])*x)) 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) dcache = DiffCache(x) for g in set(terms): # using copy of terms terms |= components(dcache.get_diff(g), x) # TODO: caching is significant factor for why permutations work at all. Change this. V = _symbols('x', len(terms)) # sort mapping expressions from largest to smallest (last is always x). 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: # # optimizing the number of permutations of mapping # assert mapping[-1][0] == x # if not, find it and correct this comment unnecessary_permutations = [mapping.pop(-1)] mappings = permutations(mapping) 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 #--- definitions for _integrate 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) for fact, mul in factors: reducibles.add(fact) 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 # nontrivial x + I*y pairs.add((x, y)) irreducibles.remove(poly) while pairs: x, y = pairs.pop() if (x, -y) in pairs: pairs.remove((x, -y)) # Choosing b with no minus sign 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)) # Note: the ordering matters here 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) # TODO: Currently it's better to use symbolic expressions here instead # of rational functions, because it's simpler and FracElement doesn't # give big speed improvement yet. This is because cancellation is slow # due to slow polynomial GCD algorithms. If this gets improved then # revise this code. candidate = poly_part/poly_denom + Add(*log_part) + Add(*atan_part) h = F - _derivation(candidate) / denom raw_numer = h.as_numer_denom()[0] # Rewrite raw_numer as a polynomial in K[coeffs][V] where K is a field # that we have to determine. We can't use simply atoms() because log(3), # sqrt(y) and similar expressions can appear, leading to non-trivial # domains. syms = set(poly_coeffs) | set(V) non_syms = set() def find_non_syms(expr): if expr.is_Integer or expr.is_Rational: pass # ignore trivial numbers elif expr in syms: pass # ignore variables 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: # TODO: Non-polynomial expression. This should have been # filtered out at an earlier stage. 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: # all free generators are identified in V 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
def eval_sum_symbolic(f, limits): from sympy.functions import harmonic, bernoulli f_orig = f (i, a, b) = limits if not f.has(i): return f*(b - a + 1) # Linearity if f.is_Mul: L, R = f.as_two_terms() if not L.has(i): sR = eval_sum_symbolic(R, (i, a, b)) if sR: return L*sR if not R.has(i): sL = eval_sum_symbolic(L, (i, a, b)) if sL: return R*sL try: f = apart(f, i) # see if it becomes an Add except PolynomialError: pass if f.is_Add: L, R = f.as_two_terms() lrsum = telescopic(L, R, (i, a, b)) if lrsum: return lrsum lsum = eval_sum_symbolic(L, (i, a, b)) rsum = eval_sum_symbolic(R, (i, a, b)) if None not in (lsum, rsum): r = lsum + rsum if not r is S.NaN: return r # Polynomial terms with Faulhaber's formula n = Wild('n') result = f.match(i**n) if result is not None: n = result[n] if n.is_Integer: if n >= 0: if (b is S.Infinity and not a is S.NegativeInfinity) or \ (a is S.NegativeInfinity and not b is S.Infinity): return S.Infinity return ((bernoulli(n + 1, b + 1) - bernoulli(n + 1, a))/(n + 1)).expand() elif a.is_Integer and a >= 1: if n == -1: return harmonic(b) - harmonic(a - 1) else: return harmonic(b, abs(n)) - harmonic(a - 1, abs(n)) if not (a.has(S.Infinity, S.NegativeInfinity) or b.has(S.Infinity, S.NegativeInfinity)): # Geometric terms c1 = Wild('c1', exclude=[i]) c2 = Wild('c2', exclude=[i]) c3 = Wild('c3', exclude=[i]) wexp = Wild('wexp') # Here we first attempt powsimp on f for easier matching with the # exponential pattern, and attempt expansion on the exponent for easier # matching with the linear pattern. e = f.powsimp().match(c1 ** wexp) if e is not None: e_exp = e.pop(wexp).expand().match(c2*i + c3) if e_exp is not None: e.update(e_exp) if e is not None: p = (c1**c3).subs(e) q = (c1**c2).subs(e) r = p*(q**a - q**(b + 1))/(1 - q) l = p*(b - a + 1) return Piecewise((l, Eq(q, S.One)), (r, True)) r = gosper_sum(f, (i, a, b)) if isinstance(r, (Mul,Add)): from sympy import ordered, Tuple non_limit = r.free_symbols - Tuple(*limits[1:]).free_symbols den = denom(together(r)) den_sym = non_limit & den.free_symbols args = [] for v in ordered(den_sym): try: s = solve(den, v) m = Eq(v, s[0]) if s else S.false if m != False: args.append((Sum(f_orig.subs(*m.args), limits).doit(), m)) break except NotImplementedError: continue args.append((r, True)) return Piecewise(*args) if not r in (None, S.NaN): return r h = eval_sum_hyper(f_orig, (i, a, b)) if h is not None: return h factored = f_orig.factor() if factored != f_orig: return eval_sum_symbolic(factored, (i, a, b))
def heurisch(f, x, rewrite=False, hints=None, mappings=None, retries=3): """ Compute indefinite integral using heuristic Risch algorithm. 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 toplevel '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": [1] http://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 components """ f = sympify(f) if not f.is_Add: indep, f = f.as_independent(x) else: indep = S.One if not f.has(x): return indep * 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, 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 g.func is exp: M = g.args[0].match(a * x**2) if M is not None: 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])) * erf(-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(-I * erf(I * (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: terms.add(acosh(sqrt(M[a] / M[b]) * x)) 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(cancel(g.diff(x)), x) # TODO: caching is significant factor for why permutations work at all. Change this. V = _symbols('x', len(terms)) mapping = dict(zip(terms, V)) rev_mapping = {} for k, v in mapping.iteritems(): rev_mapping[v] = k if mappings is None: # Pre-sort mapping in order of largest to smallest expressions (last is always x). def _sort_key(arg): return default_sort_key(arg[0].as_independent(x)[1]) mapping = sorted(mapping.items(), key=_sort_key, reverse=True) mappings = permutations(mapping) def _substitute(expr): return expr.subs(mapping) for mapping in mappings: # TODO: optimize this by not generating permutations where mapping[-1] != x. if mapping[-1][0] != x: continue mapping = list(mapping) diffs = [_substitute(cancel(g.diff(x))) 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) 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() else: 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]) else: return (S.One, p) special = {} for term in terms: if term.is_Function: if term.func is tan: special[1 + _substitute(term)**2] = False elif term.func is tanh: special[1 + _substitute(term)] = False special[1 - _substitute(term)] = False elif term.func is C.LambertW: special[_substitute(term)] = True F = _substitute(f) P, Q = F.as_numer_denom() u_split = _splitter(denom) v_split = _splitter(Q) polys = list(v_split) + [u_split[0]] + special.keys() s = u_split[0] * Mul(*[k for k, v in special.iteritems() 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 = monomials(V, A + B - 1) else: monoms = monomials(V, A + B) poly_coeffs = _symbols('A', len(monoms)) poly_part = Add( *[poly_coeffs[i] * monomial for i, monomial in enumerate(monoms)]) reducibles = set() for poly in polys: if poly.has(*V): try: factorization = factor(poly, greedy=True) except PolynomialError: factorization = poly factorization = poly if factorization.is_Mul: reducibles |= set(factorization.args) else: reducibles.add(factorization) def _integrate(field=None): irreducibles = set() for poly in reducibles: for z in poly.atoms(Symbol): if z in V: break else: continue irreducibles |= set(root_factors(poly, z, filter=field)) log_coeffs, log_part = [], [] B = _symbols('B', len(irreducibles)) for i, poly in enumerate(irreducibles): if poly.has(*V): log_coeffs.append(B[i]) log_part.append(log_coeffs[-1] * log(poly)) coeffs = poly_coeffs + log_coeffs candidate = poly_part / poly_denom + Add(*log_part) h = F - _derivation(candidate) / denom numer = h.as_numer_denom()[0].expand(force=True) equations = defaultdict(lambda: S.Zero) for term in Add.make_args(numer): coeff, dependent = term.as_independent(*V) equations[dependent] += coeff solution = solve(equations.values(), *coeffs) return (solution, candidate, coeffs) if solution else None if not (F.atoms(Symbol) - set(V)): result = _integrate('Q') if result is None: result = _integrate() else: result = _integrate() if result is not None: (solution, candidate, coeffs) = result antideriv = candidate.subs(solution) for coeff in coeffs: if coeff not in solution: antideriv = antideriv.subs(coeff, S.Zero) antideriv = antideriv.subs(rev_mapping) antideriv = cancel(antideriv).expand(force=True) 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) if result is not None: return indep * result return None
def _wilds(self, f, x, order): a = Wild('a', exclude=[x, f(x), f(x).diff(x), 0]) b = Wild('b', exclude=[x, f(x), f(x).diff(x), 0]) c = Wild('c', exclude=[x, f(x), f(x).diff(x)]) d = Wild('d', exclude=[x, f(x), f(x).diff(x)]) return a, b, c, d
def _wilds(self, f, x, order): P = Wild('P', exclude=[f(x)]) Q = Wild('Q', exclude=[f(x)]) n = Wild('n', exclude=[x, f(x), f(x).diff(x)]) return P, Q, n
def rsolve(f, y, init=None): """ Solve univariate recurrence with rational coefficients. Given `k`-th order linear recurrence `\operatorname{L} y = f`, or equivalently: .. math:: a_{k}(n) y(n+k) + a_{k-1}(n) y(n+k-1) + \dots + a_{0}(n) y(n) = f(n) where `a_{i}(n)`, for `i=0, \dots, k`, are polynomials or rational functions in `n`, and `f` is a hypergeometric function or a sum of a fixed number of pairwise dissimilar hypergeometric terms in `n`, finds all solutions or returns ``None``, if none were found. Initial conditions can be given as a dictionary in two forms: (1) ``{ n_0 : v_0, n_1 : v_1, ..., n_m : v_m }`` (2) ``{ y(n_0) : v_0, y(n_1) : v_1, ..., y(n_m) : v_m }`` or as a list ``L`` of values: ``L = [ v_0, v_1, ..., v_m ]`` where ``L[i] = v_i``, for `i=0, \dots, m`, maps to `y(n_i)`. Examples ======== Lets consider the following recurrence: .. math:: (n - 1) y(n + 2) - (n^2 + 3 n - 2) y(n + 1) + 2 n (n + 1) y(n) = 0 >>> from sympy import Function, rsolve >>> from sympy.abc import n >>> y = Function('y') >>> f = (n - 1)*y(n + 2) - (n**2 + 3*n - 2)*y(n + 1) + 2*n*(n + 1)*y(n) >>> rsolve(f, y(n)) 2**n*C0 + C1*factorial(n) >>> rsolve(f, y(n), { y(0):0, y(1):3 }) 3*2**n - 3*factorial(n) See Also ======== rsolve_poly, rsolve_ratio, rsolve_hyper """ if isinstance(f, Equality): f = f.lhs - f.rhs n = y.args[0] k = Wild('k', exclude=(n,)) # Preprocess user input to allow things like # y(n) + a*(y(n + 1) + y(n - 1))/2 f = f.expand().collect(y.func(Wild('m', integer=True))) h_part = defaultdict(lambda: S.Zero) i_part = S.Zero for g in Add.make_args(f): coeff = S.One kspec = None for h in Mul.make_args(g): if h.is_Function: if h.func == y.func: result = h.args[0].match(n + k) if result is not None: kspec = int(result[k]) else: raise ValueError( "'%s(%s+k)' expected, got '%s'" % (y.func, n, h)) else: raise ValueError( "'%s' expected, got '%s'" % (y.func, h.func)) else: coeff *= h if kspec is not None: h_part[kspec] += coeff else: i_part += coeff for k, coeff in h_part.iteritems(): h_part[k] = simplify(coeff) common = S.One for coeff in h_part.itervalues(): if coeff.is_rational_function(n): if not coeff.is_polynomial(n): common = lcm(common, coeff.as_numer_denom()[1], n) else: raise ValueError( "Polynomial or rational function expected, got '%s'" % coeff) i_numer, i_denom = i_part.as_numer_denom() if i_denom.is_polynomial(n): common = lcm(common, i_denom, n) if common is not S.One: for k, coeff in h_part.iteritems(): numer, denom = coeff.as_numer_denom() h_part[k] = numer*quo(common, denom, n) i_part = i_numer*quo(common, i_denom, n) K_min = min(h_part.keys()) if K_min < 0: K = abs(K_min) H_part = defaultdict(lambda: S.Zero) i_part = i_part.subs(n, n + K).expand() common = common.subs(n, n + K).expand() for k, coeff in h_part.iteritems(): H_part[k + K] = coeff.subs(n, n + K).expand() else: H_part = h_part K_max = max(H_part.iterkeys()) coeffs = [H_part[i] for i in xrange(K_max + 1)] result = rsolve_hyper(coeffs, -i_part, n, symbols=True) if result is None: return None solution, symbols = result if init == {} or init == []: init = None if symbols and init is not None: if type(init) is list: init = dict([(i, init[i]) for i in xrange(len(init))]) equations = [] for k, v in init.iteritems(): try: i = int(k) except TypeError: if k.is_Function and k.func == y.func: i = int(k.args[0]) else: raise ValueError("Integer or term expected, got '%s'" % k) try: eq = solution.limit(n, i) - v except NotImplementedError: eq = solution.subs(n, i) - v equations.append(eq) result = solve(equations, *symbols) if not result: return None else: solution = solution.subs(result) return solution