def check_convergence(numer, denom, n): """ Returns (h, g, p) where -- h is: > 0 for convergence of rate 1/factorial(n)**h < 0 for divergence of rate factorial(n)**(-h) = 0 for geometric or polynomial convergence or divergence -- abs(g) is: > 1 for geometric convergence of rate 1/h**n < 1 for geometric divergence of rate h**n = 1 for polynomial convergence or divergence (g < 0 indicates an alternating series) -- p is: > 1 for polynomial convergence of rate 1/n**h <= 1 for polynomial divergence of rate n**(-h) """ npol = C.Poly(numer, n) dpol = C.Poly(denom, n) p = npol.degree() q = dpol.degree() rate = q - p if rate: return rate, None, None constant = dpol.LC() / npol.LC() if abs(constant) != 1: return rate, constant, None if npol.degree() == dpol.degree() == 0: return rate, constant, 0 pc = npol.all_coeffs()[1] qc = dpol.all_coeffs()[1] return rate, constant, (qc - pc)/dpol.LC()
def evalf_log(expr, prec, options): arg = expr.args[0] workprec = prec + 10 xre, xim, xacc, _ = evalf(arg, workprec, options) if xim: # XXX: use get_abs etc instead re = evalf_log( C.log(C.Abs(arg, evaluate=False), evaluate=False), prec, options) im = mpf_atan2(xim, xre or fzero, prec) return re[0], im, re[2], prec imaginary_term = (mpf_cmp(xre, fzero) < 0) re = mpf_log(mpf_abs(xre), prec, rnd) size = fastlog(re) if prec - size > workprec: # We actually need to compute 1+x accurately, not x arg = C.Add(S.NegativeOne, arg, evaluate=False) xre, xim, _, _ = evalf_add(arg, prec, options) prec2 = workprec - fastlog(xre) # xre is now x - 1 so we add 1 back here to calculate x re = mpf_log(mpf_abs(mpf_add(xre, fone, prec2)), prec, rnd) re_acc = prec if imaginary_term: return re, mpf_pi(prec), re_acc, prec else: return re, None, re_acc, None
def evalf_piecewise(expr, prec, options): if 'subs' in options: expr = expr.subs(evalf_subs(prec, options['subs'])) newopts = options.copy() del newopts['subs'] if hasattr(expr, 'func'): return evalf(expr, prec, newopts) if type(expr) == float: return evalf(C.Float(expr), prec, newopts) if type(expr) == int: return evalf(C.Integer(expr), prec, newopts) # We still have undefined symbols raise NotImplementedError
def extract_leading_order(self, symbols, point=None): """ Returns the leading term and it's order. Examples ======== >>> from sympy.abc import x >>> (x + 1 + 1/x**5).extract_leading_order(x) ((x**(-5), O(x**(-5))),) >>> (1 + x).extract_leading_order(x) ((1, O(1)),) >>> (x + x**2).extract_leading_order(x) ((x, O(x)),) """ lst = [] symbols = list(symbols if is_sequence(symbols) else [symbols]) if not point: point = [0] * len(symbols) seq = [(f, C.Order(f, *zip(symbols, point))) for f in self.args] for ef, of in seq: for e, o in lst: if o.contains(of) and o != of: of = None break if of is None: continue new_lst = [(ef, of)] for e, o in lst: if of.contains(o) and o != of: continue new_lst.append((e, o)) lst = new_lst return tuple(lst)
def __new__(cls, *args, **options): args = list(map(_sympify, args)) args = [a for a in args if a is not cls.identity] if not options.pop('evaluate', True): return cls._from_args(args) if len(args) == 0: return cls.identity if len(args) == 1: return args[0] c_part, nc_part, order_symbols = cls.flatten(args) is_commutative = not nc_part obj = cls._from_args(c_part + nc_part, is_commutative) if order_symbols is not None: return C.Order(obj, *order_symbols) return obj
def _create_evalf_table(): global evalf_table evalf_table = { C.Symbol: evalf_symbol, C.Dummy: evalf_symbol, C.Float: lambda x, prec, options: (x._mpf_, None, prec, None), C.Rational: lambda x, prec, options: (from_rational(x.p, x.q, prec), None, prec, None), C.Integer: lambda x, prec, options: (from_int(x.p, prec), None, prec, None), C.Zero: lambda x, prec, options: (None, None, prec, None), C.One: lambda x, prec, options: (fone, None, prec, None), C.Half: lambda x, prec, options: (fhalf, None, prec, None), C.Pi: lambda x, prec, options: (mpf_pi(prec), None, prec, None), C.Exp1: lambda x, prec, options: (mpf_e(prec), None, prec, None), C.ImaginaryUnit: lambda x, prec, options: (None, fone, None, prec), C.NegativeOne: lambda x, prec, options: (fnone, None, prec, None), C.NaN : lambda x, prec, options: (fnan, None, prec, None), C.exp: lambda x, prec, options: evalf_pow(C.Pow(S.Exp1, x.args[0], evaluate=False), prec, options), C.cos: evalf_trig, C.sin: evalf_trig, C.Add: evalf_add, C.Mul: evalf_mul, C.Pow: evalf_pow, C.log: evalf_log, C.atan: evalf_atan, C.Abs: evalf_abs, C.re: evalf_re, C.im: evalf_im, C.floor: evalf_floor, C.ceiling: evalf_ceiling, C.Integral: evalf_integral, C.Sum: evalf_sum, C.Product: evalf_prod, C.Piecewise: evalf_piecewise, C.bernoulli: evalf_bernoulli, }
def evalf_sum(expr, prec, options): if 'subs' in options: expr = expr.subs(options['subs']) func = expr.function limits = expr.limits if len(limits) != 1 or len(limits[0]) != 3: raise NotImplementedError if func is S.Zero: return mpf(0), None, None, None prec2 = prec + 10 try: n, a, b = limits[0] if b != S.Infinity or a != int(a): raise NotImplementedError # Use fast hypergeometric summation if possible v = hypsum(func, n, int(a), prec2) delta = prec - fastlog(v) if fastlog(v) < -10: v = hypsum(func, n, int(a), delta) return v, None, min(prec, delta), None except NotImplementedError: # Euler-Maclaurin summation for general series eps = C.Float(2.0)**(-prec) for i in range(1, 5): m = n = 2**i * prec s, err = expr.euler_maclaurin(m=m, n=n, eps=eps, eval_integral=False) err = err.evalf() if err <= eps: break err = fastlog(evalf(abs(err), 20, options)[0]) re, im, re_acc, im_acc = evalf(s, prec2, options) if re_acc is None: re_acc = -err if im_acc is None: im_acc = -err return re, im, re_acc, im_acc
def eval(cls, arg): if isinstance(arg, Number) or arg in (True, False): return false if arg else true if arg.is_Not: return arg.args[0] # Simplify Relational objects. if isinstance(arg, C.Equality): return C.Unequality(*arg.args) if isinstance(arg, C.Unequality): return C.Equality(*arg.args) if isinstance(arg, C.StrictLessThan): return C.GreaterThan(*arg.args) if isinstance(arg, C.StrictGreaterThan): return C.LessThan(*arg.args) if isinstance(arg, C.LessThan): return C.StrictGreaterThan(*arg.args) if isinstance(arg, C.GreaterThan): return C.StrictLessThan(*arg.args)
def eval(cls, arg): if isinstance(arg, Number) or arg in (True, False): return false if arg else true # apply De Morgan Rules if arg.func is And: return Or(*[Not(a) for a in arg.args]) if arg.func is Or: return And(*[Not(a) for a in arg.args]) if arg.func is Not: return arg.args[0] # Simplify Relational objects. if isinstance(arg, C.Equality): return C.Unequality(*arg.args) if isinstance(arg, C.Unequality): return C.Equality(*arg.args) if isinstance(arg, C.StrictLessThan): return C.GreaterThan(*arg.args) if isinstance(arg, C.StrictGreaterThan): return C.LessThan(*arg.args) if isinstance(arg, C.LessThan): return C.StrictGreaterThan(*arg.args) if isinstance(arg, C.GreaterThan): return C.StrictLessThan(*arg.args)
def _matches_commutative(self, expr, repl_dict={}, old=False): """ Matches Add/Mul "pattern" to an expression "expr". repl_dict ... a dictionary of (wild: expression) pairs, that get returned with the results This function is the main workhorse for Add/Mul. For instance: >>> from sympy import symbols, Wild, sin >>> a = Wild("a") >>> b = Wild("b") >>> c = Wild("c") >>> x, y, z = symbols("x y z") >>> (a+sin(b)*c)._matches_commutative(x+sin(y)*z) {a_: x, b_: y, c_: z} In the example above, "a+sin(b)*c" is the pattern, and "x+sin(y)*z" is the expression. The repl_dict contains parts that were already matched. For example here: >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z, repl_dict={a: x}) {a_: x, b_: y, c_: z} the only function of the repl_dict is to return it in the result, e.g. if you omit it: >>> (x+sin(b)*c)._matches_commutative(x+sin(y)*z) {b_: y, c_: z} the "a: x" is not returned in the result, but otherwise it is equivalent. """ # handle simple patterns if self == expr: return repl_dict d = self._matches_simple(expr, repl_dict) if d is not None: return d # eliminate exact part from pattern: (2+a+w1+w2).matches(expr) -> (w1+w2).matches(expr-a-2) from .function import WildFunction from .symbol import Wild wild_part = [] exact_part = [] for p in ordered(self.args): if p.has(Wild, WildFunction) and (not expr.has(p)): # not all Wild should stay Wilds, for example: # (w2+w3).matches(w1) -> (w1+w3).matches(w1) -> w3.matches(0) wild_part.append(p) else: exact_part.append(p) if exact_part: exact = self.func(*exact_part) free = expr.free_symbols if free and (exact.free_symbols - free): # there are symbols in the exact part that are not # in the expr; but if there are no free symbols, let # the matching continue return None newpattern = self.func(*wild_part) newexpr = self._combine_inverse(expr, exact) if not old and (expr.is_Add or expr.is_Mul): if newexpr.count_ops() > expr.count_ops(): return None return newpattern.matches(newexpr, repl_dict) # now to real work ;) i = 0 saw = set() while expr not in saw: saw.add(expr) expr_list = (self.identity,) + tuple(ordered(self.make_args(expr))) for last_op in reversed(expr_list): for w in reversed(wild_part): d1 = w.matches(last_op, repl_dict) if d1 is not None: d2 = self.xreplace(d1).matches(expr, d1) if d2 is not None: return d2 if i == 0: if self.is_Mul: # make e**i look like Mul if expr.is_Pow and expr.exp.is_Integer: if expr.exp > 0: expr = C.Mul(*[expr.base, expr.base**(expr.exp - 1)], evaluate=False) else: expr = C.Mul(*[1/expr.base, expr.base**(expr.exp + 1)], evaluate=False) i += 1 continue elif self.is_Add: # make i*e look like Add c, e = expr.as_coeff_Mul() if abs(c) > 1: if c > 0: expr = C.Add(*[e, (c - 1)*e], evaluate=False) else: expr = C.Add(*[-e, (c + 1)*e], evaluate=False) i += 1 continue # try collection on non-Wild symbols from sympy.simplify.simplify import collect was = expr did = set() for w in reversed(wild_part): c, w = w.as_coeff_mul(Wild) free = c.free_symbols - did if free: did.update(free) expr = collect(expr, free) if expr != was: i += 0 continue break # if we didn't continue, there is nothing more to do return
def do_integral(expr, prec, options): func = expr.args[0] x, xlow, xhigh = expr.args[1] if xlow == xhigh: xlow = xhigh = 0 elif x not in func.free_symbols: # only the difference in limits matters in this case # so if there is a symbol in common that will cancel # out when taking the difference, then use that # difference if xhigh.free_symbols & xlow.free_symbols: diff = xhigh - xlow if not diff.free_symbols: xlow, xhigh = 0, diff oldmaxprec = options.get('maxprec', DEFAULT_MAXPREC) options['maxprec'] = min(oldmaxprec, 2*prec) with workprec(prec + 5): xlow = as_mpmath(xlow, prec + 15, options) xhigh = as_mpmath(xhigh, prec + 15, options) # Integration is like summation, and we can phone home from # the integrand function to update accuracy summation style # Note that this accuracy is inaccurate, since it fails # to account for the variable quadrature weights, # but it is better than nothing have_part = [False, False] max_real_term = [MINUS_INF] max_imag_term = [MINUS_INF] def f(t): re, im, re_acc, im_acc = evalf(func, mp.prec, {'subs': {x: t}}) have_part[0] = re or have_part[0] have_part[1] = im or have_part[1] max_real_term[0] = max(max_real_term[0], fastlog(re)) max_imag_term[0] = max(max_imag_term[0], fastlog(im)) if im: return mpc(re or fzero, im) return mpf(re or fzero) if options.get('quad') == 'osc': A = C.Wild('A', exclude=[x]) B = C.Wild('B', exclude=[x]) D = C.Wild('D') m = func.match(C.cos(A*x + B)*D) if not m: m = func.match(C.sin(A*x + B)*D) if not m: raise ValueError("An integrand of the form sin(A*x+B)*f(x) " "or cos(A*x+B)*f(x) is required for oscillatory quadrature") period = as_mpmath(2*S.Pi/m[A], prec + 15, options) result = quadosc(f, [xlow, xhigh], period=period) # XXX: quadosc does not do error detection yet quadrature_error = MINUS_INF else: result, quadrature_error = quadts(f, [xlow, xhigh], error=1) quadrature_error = fastlog(quadrature_error._mpf_) options['maxprec'] = oldmaxprec if have_part[0]: re = result.real._mpf_ if re == fzero: re, re_acc = scaled_zero( min(-prec, -max_real_term[0], -quadrature_error)) re = scaled_zero(re) # handled ok in evalf_integral else: re_acc = -max(max_real_term[0] - fastlog(re) - prec, quadrature_error) else: re, re_acc = None, None if have_part[1]: im = result.imag._mpf_ if im == fzero: im, im_acc = scaled_zero( min(-prec, -max_imag_term[0], -quadrature_error)) im = scaled_zero(im) # handled ok in evalf_integral else: im_acc = -max(max_imag_term[0] - fastlog(im) - prec, quadrature_error) else: im, im_acc = None, None result = re, im, re_acc, im_acc return result
def do_integral(expr, prec, options): func = expr.args[0] x, xlow, xhigh = expr.args[1] if xlow == xhigh: xlow = xhigh = 0 elif x not in func.free_symbols: # only the difference in limits matters in this case # so if there is a symbol in common that will cancel # out when taking the difference, then use that # difference if xhigh.free_symbols & xlow.free_symbols: diff = xhigh - xlow if not diff.free_symbols: xlow, xhigh = 0, diff orig = mp.prec oldmaxprec = options.get('maxprec', DEFAULT_MAXPREC) options['maxprec'] = min(oldmaxprec, 2*prec) try: mp.prec = prec + 5 xlow = as_mpmath(xlow, prec + 15, options) xhigh = as_mpmath(xhigh, prec + 15, options) # Integration is like summation, and we can phone home from # the integrand function to update accuracy summation style # Note that this accuracy is inaccurate, since it fails # to account for the variable quadrature weights, # but it is better than nothing have_part = [False, False] max_real_term = [MINUS_INF] max_imag_term = [MINUS_INF] def f(t): re, im, re_acc, im_acc = evalf(func, mp.prec, {'subs': {x: t}}) have_part[0] = re or have_part[0] have_part[1] = im or have_part[1] max_real_term[0] = max(max_real_term[0], fastlog(re)) max_imag_term[0] = max(max_imag_term[0], fastlog(im)) if im: return mpc(re or fzero, im) return mpf(re or fzero) if options.get('quad') == 'osc': A = C.Wild('A', exclude=[x]) B = C.Wild('B', exclude=[x]) D = C.Wild('D') m = func.match(C.cos(A*x + B)*D) if not m: m = func.match(C.sin(A*x + B)*D) if not m: raise ValueError("An integrand of the form sin(A*x+B)*f(x) " "or cos(A*x+B)*f(x) is required for oscillatory quadrature") period = as_mpmath(2*S.Pi/m[A], prec + 15, options) result = quadosc(f, [xlow, xhigh], period=period) # XXX: quadosc does not do error detection yet quadrature_error = MINUS_INF else: result, quadrature_error = quadts(f, [xlow, xhigh], error=1) quadrature_error = fastlog(quadrature_error._mpf_) finally: options['maxprec'] = oldmaxprec mp.prec = orig if have_part[0]: re = result.real._mpf_ if re == fzero: re, re_acc = scaled_zero( min(-prec, -max_real_term[0], -quadrature_error)) re = scaled_zero(re) # handled ok in evalf_integral else: re_acc = -max(max_real_term[0] - fastlog(re) - prec, quadrature_error) else: re, re_acc = None, None if have_part[1]: im = result.imag._mpf_ if im == fzero: im, im_acc = scaled_zero( min(-prec, -max_imag_term[0], -quadrature_error)) im = scaled_zero(im) # handled ok in evalf_integral else: im_acc = -max(max_imag_term[0] - fastlog(im) - prec, quadrature_error) else: im, im_acc = None, None result = re, im, re_acc, im_acc return result
def subs(self, *args, **kwargs): """ Substitutes old for new in an expression after sympifying args. `args` is either: - two arguments, e.g. foo.subs(old, new) - one iterable argument, e.g. foo.subs(iterable). The iterable may be o an iterable container with (old, new) pairs. In this case the replacements are processed in the order given with successive patterns possibly affecting replacements already made. o a dict or set whose key/value items correspond to old/new pairs. In this case the old/new pairs will be sorted by op count and in case of a tie, by number of args and the default_sort_key. The resulting sorted list is then processed as an iterable container (see previous). If the keyword ``simultaneous`` is True, the subexpressions will not be evaluated until all the substitutions have been made. Examples ======== >>> from sympy import pi, exp >>> from sympy.abc import x, y >>> (1 + x*y).subs(x, pi) pi*y + 1 >>> (1 + x*y).subs({x:pi, y:2}) 1 + 2*pi >>> (1 + x*y).subs([(x, pi), (y, 2)]) 1 + 2*pi >>> reps = [(y, x**2), (x, 2)] >>> (x + y).subs(reps) 6 >>> (x + y).subs(reversed(reps)) x**2 + 2 >>> (x**2 + x**4).subs(x**2, y) y**2 + y To replace only the x**2 but not the x**4, use xreplace: >>> (x**2 + x**4).xreplace({x**2: y}) x**4 + y To delay evaluation until all substitutions have been made, set the keyword ``simultaneous`` to True: >>> (x/y).subs([(x, 0), (y, 0)]) 0 >>> (x/y).subs([(x, 0), (y, 0)], simultaneous=True) nan This has the added feature of not allowing subsequent substitutions to affect those already made: >>> ((x + y)/y).subs({x + y: y, y: x + y}) 1 >>> ((x + y)/y).subs({x + y: y, y: x + y}, simultaneous=True) y/(x + y) In order to obtain a canonical result, unordered iterables are sorted by count_op length, number of arguments and by the default_sort_key to break any ties. All other iterables are left unsorted. >>> from sympy import sqrt, sin, cos, exp >>> from sympy.abc import a, b, c, d, e >>> A = (sqrt(sin(2*x)), a) >>> B = (sin(2*x), b) >>> C = (cos(2*x), c) >>> D = (x, d) >>> E = (exp(x), e) >>> expr = sqrt(sin(2*x))*sin(exp(x)*x)*cos(2*x) + sin(2*x) >>> expr.subs(dict([A,B,C,D,E])) a*c*sin(d*e) + b See Also ======== replace: replacement capable of doing wildcard-like matching, parsing of match, and conditional replacements xreplace: exact node replacement in expr tree; also capable of using matching rules """ from sympy.core.containers import Dict from sympy.utilities import default_sort_key unordered = False if len(args) == 1: sequence = args[0] if isinstance(sequence, set): unordered = True elif isinstance(sequence, (Dict, dict)): unordered = True sequence = sequence.items() elif not iterable(sequence): from sympy.utilities.misc import filldedent raise ValueError( filldedent(""" When a single argument is passed to subs it should be a dictionary of old: new pairs or an iterable of (old, new) tuples.""")) elif len(args) == 2: sequence = [args] else: raise ValueError("subs accepts either 1 or 2 arguments") sequence = list(sequence) for i in range(len(sequence)): o, n = sequence[i] so, sn = sympify(o), sympify(n) if not isinstance(so, Basic): if type(o) is str: so = C.Symbol(o) sequence[i] = (so, sn) if _aresame(so, sn): sequence[i] = None continue sequence = filter(None, sequence) if unordered: sequence = dict(sequence) if not all(k.is_Atom for k in sequence): d = {} for o, n in sequence.iteritems(): try: ops = o.count_ops(), len(o.args) except TypeError: ops = (0, 0) d.setdefault(ops, []).append((o, n)) newseq = [] for k in sorted(d.keys(), reverse=True): newseq.extend( sorted([v[0] for v in d[k]], key=default_sort_key)) sequence = [(k, sequence[k]) for k in newseq] del newseq, d else: sequence = sorted([(k, v) for (k, v) in sequence.iteritems()], key=default_sort_key) if kwargs.pop('simultaneous', False): # XXX should this be the default for dict subs? reps = {} rv = self for old, new in sequence: d = C.Dummy() rv = rv._subs(old, d) reps[d] = new if not isinstance(rv, Basic): break return rv.xreplace(reps) else: rv = self for old, new in sequence: rv = rv._subs(old, new) if not isinstance(rv, Basic): break return rv
def hypsum(expr, n, start, prec): """ Sum a rapidly convergent infinite hypergeometric series with given general term, e.g. e = hypsum(1/factorial(n), n). The quotient between successive terms must be a quotient of integer polynomials. """ from sympy import hypersimp, lambdify if prec == float('inf'): raise NotImplementedError('does not support inf prec') if start: expr = expr.subs(n, n + start) hs = hypersimp(expr, n) if hs is None: raise NotImplementedError("a hypergeometric series is required") num, den = hs.as_numer_denom() func1 = lambdify(n, num) func2 = lambdify(n, den) h, g, p = check_convergence(num, den, n) if h < 0: raise ValueError("Sum diverges like (n!)^%i" % (-h)) term = expr.subs(n, 0) if not term.is_Rational: raise NotImplementedError( "Non rational term functionality is not implemented.") # Direct summation if geometric or faster if h > 0 or (h == 0 and abs(g) > 1): term = (MPZ(term.p) << prec) // term.q s = term k = 1 while abs(term) > 5: term *= MPZ(func1(k - 1)) term //= MPZ(func2(k - 1)) s += term k += 1 return from_man_exp(s, -prec) else: alt = g < 0 if abs(g) < 1: raise ValueError("Sum diverges like (%i)^n" % abs(1 / g)) if p < 1 or (p == 1 and not alt): raise ValueError("Sum diverges like n^%i" % (-p)) # We have polynomial convergence: use Richardson extrapolation vold = None ndig = prec_to_dps(prec) while True: # Need to use at least quad precision because a lot of cancellation # might occur in the extrapolation process; we check the answer to # make sure that the desired precision has been reached, too. prec2 = 4 * prec term0 = (MPZ(term.p) << prec2) // term.q def summand(k, _term=[term0]): if k: k = int(k) _term[0] *= MPZ(func1(k - 1)) _term[0] //= MPZ(func2(k - 1)) return make_mpf(from_man_exp(_term[0], -prec2)) with workprec(prec): v = nsum(summand, [0, mpmath_inf], method='richardson') vf = C.Float(v, ndig) if vold is not None and vold == vf: break prec += prec # double precision each time vold = vf return v._mpf_
def _eval_product(self, term, limits): from sympy.concrete.delta import deltaproduct, _has_simple_delta from sympy.concrete.summations import summation from sympy.functions import KroneckerDelta (k, a, n) = limits if k not in term.free_symbols: return term**(n - a + 1) if a == n: return term.subs(k, a) if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]): return deltaproduct(term, limits) dif = n - a if dif.is_Integer: return Mul(*[term.subs(k, a + i) for i in xrange(dif + 1)]) elif term.is_polynomial(k): poly = term.as_poly(k) A = B = Q = S.One all_roots = roots(poly) M = 0 for r, m in all_roots.items(): M += m A *= C.RisingFactorial(a - r, n - a + 1)**m Q *= (n - r)**m if M < poly.degree(): arg = quo(poly, Q.as_poly(k)) B = self.func(arg, (k, a, n)).doit() return poly.LC()**(n - a + 1) * A * B elif term.is_Add: p, q = term.as_numer_denom() p = self._eval_product(p, (k, a, n)) q = self._eval_product(q, (k, a, n)) return p / q elif term.is_Mul: exclude, include = [], [] for t in term.args: p = self._eval_product(t, (k, a, n)) if p is not None: exclude.append(p) else: include.append(t) if not exclude: return None else: arg = term._new_rawargs(*include) A = Mul(*exclude) B = self.func(arg, (k, a, n)).doit() return A * B elif term.is_Pow: if not term.base.has(k): s = summation(term.exp, (k, a, n)) return term.base**s elif not term.exp.has(k): p = self._eval_product(term.base, (k, a, n)) if p is not None: return p**term.exp elif isinstance(term, Product): evaluated = term.doit() f = self._eval_product(evaluated, limits) if f is None: return self.func(evaluated, limits) else: return f