def _eval_nseries(self, x, n, logx): # NOTE Please see the comment at the beginning of this file, labelled # IMPORTANT. from sympy import cancel 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 = r.get(r, S.One) #l = r.get(l, S.Zero) 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 # TODO new and probably slow s = self.args[0].nseries(x, n=n, logx=logx) while s.is_Order: n += 1 s = self.args[0].nseries(x, n=n, logx=logx) a, b = s.leadterm(x) p = cancel(s/(a*x**b) - 1) g = None l = [] for i in xrange(n + 2): g = log.taylor_term(i, p, g) g = g.nseries(x, n=n, logx=logx) l.append(g) return log(a) + b*logx + Add(*l) + C.Order(p**n, x)
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 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 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 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 _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 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 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
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 not r in (None, S.NaN): return r return eval_sum_hyper(f_orig, (i, a, b))
def _eval_integral(self, f, x, meijerg=None, risch=None, manual=None, conds='piecewise'): """ Calculate the anti-derivative to the function f(x). The following algorithms are applied (roughly in this order): 1. Simple heuristics (based on pattern matching and integral table): - most frequently used functions (e.g. polynomials, products of trig functions) 2. Integration of rational functions: - A complete algorithm for integrating rational functions is implemented (the Lazard-Rioboo-Trager algorithm). The algorithm also uses the partial fraction decomposition algorithm implemented in apart() as a preprocessor to make this process faster. Note that the integral of a rational function is always elementary, but in general, it may include a RootSum. 3. Full Risch algorithm: - The Risch algorithm is a complete decision procedure for integrating elementary functions, which means that given any elementary function, it will either compute an elementary antiderivative, or else prove that none exists. Currently, part of transcendental case is implemented, meaning elementary integrals containing exponentials, logarithms, and (soon!) trigonometric functions can be computed. The algebraic case, e.g., functions containing roots, is much more difficult and is not implemented yet. - If the routine fails (because the integrand is not elementary, or because a case is not implemented yet), it continues on to the next algorithms below. If the routine proves that the integrals is nonelementary, it still moves on to the algorithms below, because we might be able to find a closed-form solution in terms of special functions. If risch=True, however, it will stop here. 4. The Meijer G-Function algorithm: - This algorithm works by first rewriting the integrand in terms of very general Meijer G-Function (meijerg in SymPy), integrating it, and then rewriting the result back, if possible. This algorithm is particularly powerful for definite integrals (which is actually part of a different method of Integral), since it can compute closed-form solutions of definite integrals even when no closed-form indefinite integral exists. But it also is capable of computing many indefinite integrals as well. - Another advantage of this method is that it can use some results about the Meijer G-Function to give a result in terms of a Piecewise expression, which allows to express conditionally convergent integrals. - Setting meijerg=True will cause integrate() to use only this method. 5. The "manual integration" algorithm: - This algorithm tries to mimic how a person would find an antiderivative by hand, for example by looking for a substitution or applying integration by parts. This algorithm does not handle as many integrands but can return results in a more familiar form. - Sometimes this algorithm can evaluate parts of an integral; in this case integrate() will try to evaluate the rest of the integrand using the other methods here. - Setting manual=True will cause integrate() to use only this method. 6. The Heuristic Risch algorithm: - This is a heuristic version of the Risch algorithm, meaning that it is not deterministic. This is tried as a last resort because it can be very slow. It is still used because not enough of the full Risch algorithm is implemented, so that there are still some integrals that can only be computed using this method. The goal is to implement enough of the Risch and Meijer G methods so that this can be deleted. """ from sympy.integrals.risch import risch_integrate if risch: try: return risch_integrate(f, x, conds=conds) except NotImplementedError: return None if manual: try: result = manualintegrate(f, x) if result is not None and result.func != Integral: return result except (ValueError, PolynomialError): pass # if it is a poly(x) then let the polynomial integrate itself (fast) # # It is important to make this check first, otherwise the other code # will return a sympy expression instead of a Polynomial. # # see Polynomial for details. if isinstance(f, Poly) and not meijerg: return f.integrate(x) # Piecewise antiderivatives need to call special integrate. if f.func is Piecewise: return f._eval_integral(x) # let's cut it short if `f` does not depend on `x` if not f.has(x): return f * x # try to convert to poly(x) and then integrate if successful (fast) poly = f.as_poly(x) if poly is not None and not meijerg: return poly.integrate().as_expr() if risch is not False: try: result, i = risch_integrate(f, x, separate_integral=True, conds=conds) except NotImplementedError: pass else: if i: # There was a nonelementary integral. Try integrating it. return result + i.doit(risch=False) else: return result # since Integral(f=g1+g2+...) == Integral(g1) + Integral(g2) + ... # we are going to handle Add terms separately, # if `f` is not Add -- we only have one term # Note that in general, this is a bad idea, because Integral(g1) + # Integral(g2) might not be computable, even if Integral(g1 + g2) is. # For example, Integral(x**x + x**x*log(x)). But many heuristics only # work term-wise. So we compute this step last, after trying # risch_integrate. We also try risch_integrate again in this loop, # because maybe the integral is a sum of an elementary part and a # nonelementary part (like erf(x) + exp(x)). risch_integrate() is # quite fast, so this is acceptable. parts = [] args = Add.make_args(f) for g in args: coeff, g = g.as_independent(x) # g(x) = const if g is S.One and not meijerg: parts.append(coeff * x) continue # g(x) = expr + O(x**n) order_term = g.getO() if order_term is not None: h = self._eval_integral(g.removeO(), x) if h is not None: h_order_expr = self._eval_integral(order_term.expr, x) if h_order_expr is not None: h_order_term = order_term.func(h_order_expr, *order_term.variables) parts.append(coeff * (h + h_order_term)) continue # NOTE: if there is O(x**n) and we fail to integrate then there is # no point in trying other methods because they will fail anyway. return None # c # g(x) = (a*x+b) if g.is_Pow and not g.exp.has(x) and not meijerg: a = Wild('a', exclude=[x]) b = Wild('b', exclude=[x]) M = g.base.match(a * x + b) if M is not None: if g.exp == -1: h = C.log(g.base) elif conds != 'piecewise': h = g.base**(g.exp + 1) / (g.exp + 1) else: h1 = C.log(g.base) h2 = g.base**(g.exp + 1) / (g.exp + 1) h = Piecewise((h1, Eq(g.exp, -1)), (h2, True)) parts.append(coeff * h / M[a]) continue # poly(x) # g(x) = ------- # poly(x) if g.is_rational_function(x) and not meijerg: parts.append(coeff * ratint(g, x)) continue if not meijerg: # g(x) = Mul(trig) h = trigintegrate(g, x, conds=conds) if h is not None: parts.append(coeff * h) continue # g(x) has at least a DiracDelta term h = deltaintegrate(g, x) if h is not None: parts.append(coeff * h) continue # Try risch again. if risch is not False: try: h, i = risch_integrate(g, x, separate_integral=True, conds=conds) except NotImplementedError: h = None else: if i: h = h + i.doit(risch=False) parts.append(coeff * h) continue # fall back to heurisch try: if conds == 'piecewise': h = heurisch_wrapper(g, x, hints=[]) else: h = heurisch(g, x, hints=[]) except PolynomialError: # XXX: this exception means there is a bug in the # implementation of heuristic Risch integration # algorithm. h = None else: h = None if meijerg is not False and h is None: # rewrite using G functions try: h = meijerint_indefinite(g, x) except NotImplementedError: from sympy.integrals.meijerint import _debug _debug('NotImplementedError from meijerint_definite') res = None if h is not None: parts.append(coeff * h) continue if h is None and manual is not False: try: result = manualintegrate(g, x) if result is not None and not isinstance(result, Integral): if result.has(Integral): # try to have other algorithms do the integrals # manualintegrate can't handle result = result.func(*[ arg.doit( manual=False) if arg.has(Integral) else arg for arg in result.args ]).expand(multinomial=False, log=False, power_exp=False, power_base=False) if not result.has(Integral): parts.append(coeff * result) continue except (ValueError, PolynomialError): # can't handle some SymPy expressions pass # if we failed maybe it was because we had # a product that could have been expanded, # so let's try an expansion of the whole # thing before giving up; we don't try this # at the outset because there are things # that cannot be solved unless they are # NOT expanded e.g., x**x*(1+log(x)). There # should probably be a checker somewhere in this # routine to look for such cases and try to do # collection on the expressions if they are already # in an expanded form if not h and len(args) == 1: f = f.expand(mul=True, deep=False) if f.is_Add: # Note: risch will be identical on the expanded # expression, but maybe it will be able to pick out parts, # like x*(exp(x) + erf(x)). return self._eval_integral(f, x, meijerg=meijerg, risch=risch, conds=conds) if h is not None: parts.append(coeff * h) else: return None return Add(*parts)
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 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 _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 test_sympy__core__symbol__Wild(): from sympy.core.symbol import Wild assert _test_args(Wild('x', exclude=[x]))
def test_Wild(): sT(Wild('x', even=True), "Wild('x', even=True)")
def _compute_fps(f, x, x0, dir, hyper, order, rational, full, extract=True): """Recursive wrapper to compute fps. See :func:`compute_fps` for details. """ if x0 in [S.Infinity, -S.Infinity]: dir = S.One if x0 is S.Infinity else -S.One temp = f.subs(x, 1/x) result = _compute_fps(temp, x, 0, dir, hyper, order, rational, full) if result is None: return None return (result[0], result[1].subs(x, 1/x), result[2].subs(x, 1/x), result[3].subs(x, 1/x)) elif x0 or dir == -S.One: if dir == -S.One: rep = -x + x0 rep2 = -x rep2b = x0 else: rep = x + x0 rep2 = x rep2b = -x0 temp = f.subs(x, rep) result = _compute_fps(temp, x, 0, S.One, hyper, order, rational, full) if result is None: return None return (result[0], result[1].subs(x, rep2 + rep2b), result[2].subs(x, rep2 + rep2b), result[3].subs(x, rep2 + rep2b)) if f.is_polynomial(x): return None # extract x**n from f(x) mul = S.One if extract and f.free_symbols.difference(set([x])): m = Wild('m') n = Wild('n', exclude=[m]) f = f.factor().powsimp() s = f.match(x**n*m) if s[n]: for t in Add.make_args(s[n]): if t.has(Symbol): mul *= (x)**t if mul is not S.One: f = (f / mul) f = f.expand() # Break instances of Add # this allows application of different # algorithms on different terms increasing the # range of admissible functions. if isinstance(f, Add): result = False ak = sequence(S.Zero, (0, oo)) ind, xk = S.Zero, None for t in Add.make_args(f): res = _compute_fps(t, x, 0, S.One, hyper, order, rational, full, False) if res: if not result: result = True xk = res[1] if res[0].start > ak.start: seq = ak s, f = ak.start, res[0].start else: seq = res[0] s, f = res[0].start, ak.start save = Add(*[z[0]*z[1] for z in zip(seq[0:(f - s)], xk[s:f])]) ak += res[0] ind += res[2] + save else: ind += t if result: return ak, xk, ind, mul return None result = None # from here on it's x0=0 and dir=1 handling k = Dummy('k') if rational: result = rational_algorithm(f, x, k, order, full) if result is None and hyper: result = hyper_algorithm(f, x, k, order) if result is None: return None ak = sequence(result[0], (k, result[2], oo)) xk = sequence(x**k, (k, 0, oo)) ind = result[1] return ak, xk, ind, mul
def test_core_symbol(): for c in (Dummy, Dummy("x", False), Symbol, Symbol("x", False), Wild, Wild("x")): check(c)
def test_issue_6103(): x = Symbol('x') a = Wild('a') assert (-I*x*oo).match(I*a*oo) == {a: -x}
def test_issue_4319(): x, y = symbols('x y') p = -x*(S.One/8 - y) ans = {S.Zero, y - S.One/8} def ok(pat): assert set(p.match(pat).values()) == ans ok(Wild("coeff", exclude=[x])*x + Wild("rest")) ok(Wild("w", exclude=[x])*x + Wild("rest")) ok(Wild("coeff", exclude=[x])*x + Wild("rest")) ok(Wild("w", exclude=[x])*x + Wild("rest")) ok(Wild("e", exclude=[x])*x + Wild("rest")) ok(Wild("ress", exclude=[x])*x + Wild("rest")) ok(Wild("resu", exclude=[x])*x + Wild("rest"))
#assert ( t.diff(x,2)*r[a]/t ).expand() == eq return solve_ODE_1(f(x), x) raise NotImplementedError("solve_ODE_second_order: cannot solve " + str(eq)) def solve_ODE_1(f, x): """ (x*exp(-f(x)))'' = 0 """ C1 = Symbol("C1") C2 = Symbol("C2") return -C.log(C1 + C2 / x) x = Symbol('x', dummy=True) a, b, c, d, e, f, g, h = [Wild(t, exclude=[x]) for t in 'abcdefgh'] patterns = None def _generate_patterns(): """Generates patterns for transcendental equations. This is lazily calculated (called) in the tsolve() function and stored in the patterns global variable. """ tmp1 = f**(h - (c * g / b)) tmp2 = (-e * tmp1 / a)**(1 / d) global patterns patterns = [ (a * (b * x + c)**d + e, ((-(e / a))**(1 / d) - c) / 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 _eval_nseries(self, x, n): from sympy import powsimp arg = self.args[0] k, l = Wild("k"), Wild("l") r = arg.match(k*x**l) if r is not None: #k = r.get(r, S.One) #l = r.get(l, S.Zero) k, l = r[k], r[l] if l != 0 and not l.has(x) and not k.has(x): r = log(k) + l*log(x) # XXX true regardless of assumptions? return r order = C.Order(x**n, x) arg = self.args[0] use_lt = not C.Order(1, x).contains(arg) if not use_lt: arg0 = arg.limit(x, 0) use_lt = (arg0 is S.Zero) if use_lt: # singularity, #example: self = log(sin(x)) # arg = (arg / lt) * lt lt = arg.as_leading_term(x) # arg = sin(x); lt = x a = powsimp((arg/lt).expand(), deep=True, combine='exp') # a = sin(x)/x # the idea is to recursively call log(a).series(), but one needs to # make sure that log(sin(x)/x) doesn't get "simplified" to # -log(x)+log(sin(x)) and an infinite recursion occurs, see also the # issue 252. obj = log(lt) + log(a).nseries(x, n=n) else: # arg -> arg0 + (arg - arg0) -> arg0 * (1 + (arg/arg0 - 1)) z = (arg/arg0 - 1) o = C.Order(z, x) if o is S.Zero: return log(1 + z) + log(arg0) if o.expr.is_number: e = log(order.expr*x)/log(x) else: e = log(order.expr)/log(o.expr) n = e.limit(x, 0) + 1 if n.is_unbounded: # requested accuracy gives infinite series, # order is probably nonpolynomial e.g. O(exp(-1/x), x). return log(1 + z) + log(arg0) # XXX was int or floor intended? int used to behave like floor try: n = int(n) except TypeError: #well, the n is something more complicated (like 1+log(2)) n = int(n.evalf()) + 1 # XXX why is 1 being added? assert n>=0, `n` l = [] g = None for i in xrange(n + 2): g = log.taylor_term(i, z, g) g = g.nseries(x, n=n) l.append(g) obj = Add(*l) + log(arg0) obj2 = expand_log(powsimp(obj, deep=True, combine='exp')) if obj2 != obj: r = obj2.nseries(x, n=n) else: r = obj if r == self: return self return r + order
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 tsolve(eq, sym): """ Solves a transcendental equation with respect to the given symbol. Various equations containing mixed linear terms, powers, and logarithms, can be solved. Only a single solution is returned. This solution is generally not unique. In some cases, a complex solution may be returned even though a real solution exists. >>> from sympy import * >>> x = Symbol('x') >>> tsolve(3**(2*x+5)-4, x) [(-5*log(3) + log(4))/(2*log(3))] >>> tsolve(log(x) + 2*x, x) [1/2*LambertW(2)] """ if patterns is None: _generate_patterns() eq = sympify(eq) if isinstance(eq, Equality): eq = eq.lhs - eq.rhs sym = sympify(sym) eq2 = eq.subs(sym, x) # First see if the equation has a linear factor # In that case, the other factor can contain x in any way (as long as it # is finite), and we have a direct solution r = Wild('r') m = eq2.match((a * x + b) * r) if m and m[a]: return [(-b / a).subs(m).subs(x, sym)] for p, sol in patterns: m = eq2.match(p) if m: return [sol.subs(m).subs(x, sym)] # let's also try to inverse the equation lhs = eq rhs = S.Zero while True: indep, dep = lhs.as_independent(sym) # dep + indep == rhs if lhs.is_Add: # this indicates we have done it all if indep is S.Zero: break lhs = dep rhs -= indep # dep * indep == rhs else: # this indicates we have done it all if indep is S.One: break lhs = dep rhs /= indep # -1 # f(x) = g -> x = f (g) if lhs.is_Function and lhs.nargs == 1 and hasattr(lhs, 'inverse'): rhs = lhs.inverse()(rhs) lhs = lhs.args[0] sol = solve(lhs - rhs, sym) return sol elif lhs.is_Add: # just a simple case - we do variable substitution for first function, # and if it removes all functions - let's call solve. # x -x -1 # UC: e + e = y -> t + t = y t = Symbol('t', dummy=True) terms = lhs.args # find first term which is Function for f1 in lhs.args: if f1.is_Function: break else: assert False, 'tsolve: at least one Function expected at this point' # perform the substitution lhs_ = lhs.subs(f1, t) # if no Functions left, we can proceed with usual solve if not (lhs_.is_Function or any(term.is_Function for term in lhs_.args)): cv_sols = solve(lhs_ - rhs, t) cv_inv = solve(t - f1, sym)[0] sols = list() for sol in cv_sols: sols.append(cv_inv.subs(t, sol)) return sols raise ValueError("unable to solve the equation")
def classify_pde(eq, func=None, dict=False, **kwargs): """ Returns a tuple of possible pdsolve() classifications for a PDE. The tuple is ordered so that first item is the classification that pdsolve() uses to solve the PDE by default. In general, classifications at the near the beginning of the list will produce better solutions faster than those near the end, thought there are always exceptions. To make pdsolve use a different classification, use pdsolve(PDE, func, hint=<classification>). See also the pdsolve() docstring for different meta-hints you can use. If ``dict`` is true, classify_pde() will return a dictionary of hint:match expression terms. This is intended for internal use by pdsolve(). Note that because dictionaries are ordered arbitrarily, this will most likely not be in the same order as the tuple. You can get help on different hints by doing help(pde.pde_hintname), where hintname is the name of the hint without "_Integral". See sympy.pde.allhints or the sympy.pde docstring for a list of all supported hints that can be returned from classify_pde. Examples ======== >>> from sympy.solvers.pde import classify_pde >>> from sympy import Function, diff, Eq >>> from sympy.abc import x, y >>> f = Function('f') >>> u = f(x, y) >>> ux = u.diff(x) >>> uy = u.diff(y) >>> eq = Eq(1 + (2*(ux/u)) + (3*(uy/u))) >>> classify_pde(eq) ('1st_linear_constant_coeff_homogeneous',) """ prep = kwargs.pop('prep', True) if func and len(func.args) != 2: raise NotImplementedError("Right now only partial " "differential equations of two variables are supported") if prep or func is None: prep, func_ = _preprocess(eq, func) if func is None: func = func_ if isinstance(eq, Equality): if eq.rhs != 0: return classify_pde(eq.lhs - eq.rhs, func) eq = eq.lhs f = func.func x = func.args[0] y = func.args[1] fx = f(x,y).diff(x) fy = f(x,y).diff(y) # TODO : For now pde.py uses support offered by the ode_order function # to find the order with respect to a multi-variable function. An # improvement could be to classify the order of the PDE on the basis of # individual variables. order = ode_order(eq, f(x,y)) # hint:matchdict or hint:(tuple of matchdicts) # Also will contain "default":<default hint> and "order":order items. matching_hints = {'order': order} if not order: if dict: matching_hints["default"] = None return matching_hints else: return () eq = expand(eq) a = Wild('a', exclude = [f(x,y)]) b = Wild('b', exclude = [f(x,y), fx, fy, x, y]) c = Wild('c', exclude = [f(x,y), fx, fy, x, y]) d = Wild('d', exclude = [f(x,y), fx, fy, x, y]) e = Wild('e', exclude = [f(x,y), fx, fy]) n = Wild('n', exclude = [x, y]) # Try removing the smallest power of f(x,y) # from the highest partial derivatives of f(x,y) reduced_eq = None if eq.is_Add: var = set(combinations_with_replacement((x,y), order)) dummyvar = deepcopy(var) power = None for i in var: coeff = eq.coeff(f(x,y).diff(*i)) if coeff != 1: match = coeff.match(a*f(x,y)**n) if match and match[a]: power = match[n] dummyvar.remove(i) break dummyvar.remove(i) for i in dummyvar: coeff = eq.coeff(f(x,y).diff(*i)) if coeff != 1: match = coeff.match(a*f(x,y)**n) if match and match[a] and match[n] < power: power = match[n] if power: den = f(x,y)**power reduced_eq = Add(*[arg/den for arg in eq.args]) if not reduced_eq: reduced_eq = eq if order == 1: reduced_eq = collect(reduced_eq, f(x, y)) r = reduced_eq.match(b*fx + c*fy + d*f(x,y) + e) if r: if not r[e]: ## Linear first-order homogeneous partial-differential ## equation with constant coefficients r.update({'b': b, 'c': c, 'd': d}) matching_hints["1st_linear_constant_coeff_homogeneous"] = r else: if r[b]**2 + r[c]**2 != 0: ## Linear first-order general partial-differential ## equation with constant coefficients r.update({'b': b, 'c': c, 'd': d, 'e': e}) matching_hints["1st_linear_constant_coeff"] = r matching_hints[ "1st_linear_constant_coeff_Integral"] = r else: b = Wild('b', exclude=[f(x, y), fx, fy]) c = Wild('c', exclude=[f(x, y), fx, fy]) d = Wild('d', exclude=[f(x, y), fx, fy]) r = reduced_eq.match(b*fx + c*fy + d*f(x,y) + e) if r: r.update({'b': b, 'c': c, 'd': d, 'e': e}) matching_hints["1st_linear_variable_coeff"] = r # Order keys based on allhints. retlist = [] for i in allhints: if i in matching_hints: retlist.append(i) if dict: # Dictionaries are ordered arbitrarily, so make note of which # hint would come first for pdsolve(). Use an ordered dict in Py 3. matching_hints["default"] = None matching_hints["ordered_hints"] = tuple(retlist) for i in allhints: if i in matching_hints: matching_hints["default"] = i break return matching_hints else: return tuple(retlist)
def test_issue_3539(): a = Wild('a') x = Symbol('x') assert (x - 2).match(a - x) is None assert (6/x).match(a*x) is None assert (6/x**2).match(a/x) == {a: 6/x}