Coverage for /usr/lib/python3/dist-packages/sympy/concrete/expr_with_limits.py: 17%
292 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from sympy.core.add import Add
2from sympy.core.containers import Tuple
3from sympy.core.expr import Expr
4from sympy.core.function import AppliedUndef, UndefinedFunction
5from sympy.core.mul import Mul
6from sympy.core.relational import Equality, Relational
7from sympy.core.singleton import S
8from sympy.core.symbol import Symbol, Dummy
9from sympy.core.sympify import sympify
10from sympy.functions.elementary.piecewise import (piecewise_fold,
11 Piecewise)
12from sympy.logic.boolalg import BooleanFunction
13from sympy.matrices.matrices import MatrixBase
14from sympy.sets.sets import Interval, Set
15from sympy.sets.fancysets import Range
16from sympy.tensor.indexed import Idx
17from sympy.utilities import flatten
18from sympy.utilities.iterables import sift, is_sequence
19from sympy.utilities.exceptions import sympy_deprecation_warning
22def _common_new(cls, function, *symbols, discrete, **assumptions):
23 """Return either a special return value or the tuple,
24 (function, limits, orientation). This code is common to
25 both ExprWithLimits and AddWithLimits."""
26 function = sympify(function)
28 if isinstance(function, Equality):
29 # This transforms e.g. Integral(Eq(x, y)) to Eq(Integral(x), Integral(y))
30 # but that is only valid for definite integrals.
31 limits, orientation = _process_limits(*symbols, discrete=discrete)
32 if not (limits and all(len(limit) == 3 for limit in limits)):
33 sympy_deprecation_warning(
34 """
35 Creating a indefinite integral with an Eq() argument is
36 deprecated.
38 This is because indefinite integrals do not preserve equality
39 due to the arbitrary constants. If you want an equality of
40 indefinite integrals, use Eq(Integral(a, x), Integral(b, x))
41 explicitly.
42 """,
43 deprecated_since_version="1.6",
44 active_deprecations_target="deprecated-indefinite-integral-eq",
45 stacklevel=5,
46 )
48 lhs = function.lhs
49 rhs = function.rhs
50 return Equality(cls(lhs, *symbols, **assumptions), \
51 cls(rhs, *symbols, **assumptions))
53 if function is S.NaN:
54 return S.NaN
56 if symbols:
57 limits, orientation = _process_limits(*symbols, discrete=discrete)
58 for i, li in enumerate(limits):
59 if len(li) == 4:
60 function = function.subs(li[0], li[-1])
61 limits[i] = Tuple(*li[:-1])
62 else:
63 # symbol not provided -- we can still try to compute a general form
64 free = function.free_symbols
65 if len(free) != 1:
66 raise ValueError(
67 "specify dummy variables for %s" % function)
68 limits, orientation = [Tuple(s) for s in free], 1
70 # denest any nested calls
71 while cls == type(function):
72 limits = list(function.limits) + limits
73 function = function.function
75 # Any embedded piecewise functions need to be brought out to the
76 # top level. We only fold Piecewise that contain the integration
77 # variable.
78 reps = {}
79 symbols_of_integration = {i[0] for i in limits}
80 for p in function.atoms(Piecewise):
81 if not p.has(*symbols_of_integration):
82 reps[p] = Dummy()
83 # mask off those that don't
84 function = function.xreplace(reps)
85 # do the fold
86 function = piecewise_fold(function)
87 # remove the masking
88 function = function.xreplace({v: k for k, v in reps.items()})
90 return function, limits, orientation
93def _process_limits(*symbols, discrete=None):
94 """Process the list of symbols and convert them to canonical limits,
95 storing them as Tuple(symbol, lower, upper). The orientation of
96 the function is also returned when the upper limit is missing
97 so (x, 1, None) becomes (x, None, 1) and the orientation is changed.
98 In the case that a limit is specified as (symbol, Range), a list of
99 length 4 may be returned if a change of variables is needed; the
100 expression that should replace the symbol in the expression is
101 the fourth element in the list.
102 """
103 limits = []
104 orientation = 1
105 if discrete is None:
106 err_msg = 'discrete must be True or False'
107 elif discrete:
108 err_msg = 'use Range, not Interval or Relational'
109 else:
110 err_msg = 'use Interval or Relational, not Range'
111 for V in symbols:
112 if isinstance(V, (Relational, BooleanFunction)):
113 if discrete:
114 raise TypeError(err_msg)
115 variable = V.atoms(Symbol).pop()
116 V = (variable, V.as_set())
117 elif isinstance(V, Symbol) or getattr(V, '_diff_wrt', False):
118 if isinstance(V, Idx):
119 if V.lower is None or V.upper is None:
120 limits.append(Tuple(V))
121 else:
122 limits.append(Tuple(V, V.lower, V.upper))
123 else:
124 limits.append(Tuple(V))
125 continue
126 if is_sequence(V) and not isinstance(V, Set):
127 if len(V) == 2 and isinstance(V[1], Set):
128 V = list(V)
129 if isinstance(V[1], Interval): # includes Reals
130 if discrete:
131 raise TypeError(err_msg)
132 V[1:] = V[1].inf, V[1].sup
133 elif isinstance(V[1], Range):
134 if not discrete:
135 raise TypeError(err_msg)
136 lo = V[1].inf
137 hi = V[1].sup
138 dx = abs(V[1].step) # direction doesn't matter
139 if dx == 1:
140 V[1:] = [lo, hi]
141 else:
142 if lo is not S.NegativeInfinity:
143 V = [V[0]] + [0, (hi - lo)//dx, dx*V[0] + lo]
144 else:
145 V = [V[0]] + [0, S.Infinity, -dx*V[0] + hi]
146 else:
147 # more complicated sets would require splitting, e.g.
148 # Union(Interval(1, 3), interval(6,10))
149 raise NotImplementedError(
150 'expecting Range' if discrete else
151 'Relational or single Interval' )
152 V = sympify(flatten(V)) # list of sympified elements/None
153 if isinstance(V[0], (Symbol, Idx)) or getattr(V[0], '_diff_wrt', False):
154 newsymbol = V[0]
155 if len(V) == 3:
156 # general case
157 if V[2] is None and V[1] is not None:
158 orientation *= -1
159 V = [newsymbol] + [i for i in V[1:] if i is not None]
161 lenV = len(V)
162 if not isinstance(newsymbol, Idx) or lenV == 3:
163 if lenV == 4:
164 limits.append(Tuple(*V))
165 continue
166 if lenV == 3:
167 if isinstance(newsymbol, Idx):
168 # Idx represents an integer which may have
169 # specified values it can take on; if it is
170 # given such a value, an error is raised here
171 # if the summation would try to give it a larger
172 # or smaller value than permitted. None and Symbolic
173 # values will not raise an error.
174 lo, hi = newsymbol.lower, newsymbol.upper
175 try:
176 if lo is not None and not bool(V[1] >= lo):
177 raise ValueError("Summation will set Idx value too low.")
178 except TypeError:
179 pass
180 try:
181 if hi is not None and not bool(V[2] <= hi):
182 raise ValueError("Summation will set Idx value too high.")
183 except TypeError:
184 pass
185 limits.append(Tuple(*V))
186 continue
187 if lenV == 1 or (lenV == 2 and V[1] is None):
188 limits.append(Tuple(newsymbol))
189 continue
190 elif lenV == 2:
191 limits.append(Tuple(newsymbol, V[1]))
192 continue
194 raise ValueError('Invalid limits given: %s' % str(symbols))
196 return limits, orientation
199class ExprWithLimits(Expr):
200 __slots__ = ('is_commutative',)
202 def __new__(cls, function, *symbols, **assumptions):
203 from sympy.concrete.products import Product
204 pre = _common_new(cls, function, *symbols,
205 discrete=issubclass(cls, Product), **assumptions)
206 if isinstance(pre, tuple):
207 function, limits, _ = pre
208 else:
209 return pre
211 # limits must have upper and lower bounds; the indefinite form
212 # is not supported. This restriction does not apply to AddWithLimits
213 if any(len(l) != 3 or None in l for l in limits):
214 raise ValueError('ExprWithLimits requires values for lower and upper bounds.')
216 obj = Expr.__new__(cls, **assumptions)
217 arglist = [function]
218 arglist.extend(limits)
219 obj._args = tuple(arglist)
220 obj.is_commutative = function.is_commutative # limits already checked
222 return obj
224 @property
225 def function(self):
226 """Return the function applied across limits.
228 Examples
229 ========
231 >>> from sympy import Integral
232 >>> from sympy.abc import x
233 >>> Integral(x**2, (x,)).function
234 x**2
236 See Also
237 ========
239 limits, variables, free_symbols
240 """
241 return self._args[0]
243 @property
244 def kind(self):
245 return self.function.kind
247 @property
248 def limits(self):
249 """Return the limits of expression.
251 Examples
252 ========
254 >>> from sympy import Integral
255 >>> from sympy.abc import x, i
256 >>> Integral(x**i, (i, 1, 3)).limits
257 ((i, 1, 3),)
259 See Also
260 ========
262 function, variables, free_symbols
263 """
264 return self._args[1:]
266 @property
267 def variables(self):
268 """Return a list of the limit variables.
270 >>> from sympy import Sum
271 >>> from sympy.abc import x, i
272 >>> Sum(x**i, (i, 1, 3)).variables
273 [i]
275 See Also
276 ========
278 function, limits, free_symbols
279 as_dummy : Rename dummy variables
280 sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable
281 """
282 return [l[0] for l in self.limits]
284 @property
285 def bound_symbols(self):
286 """Return only variables that are dummy variables.
288 Examples
289 ========
291 >>> from sympy import Integral
292 >>> from sympy.abc import x, i, j, k
293 >>> Integral(x**i, (i, 1, 3), (j, 2), k).bound_symbols
294 [i, j]
296 See Also
297 ========
299 function, limits, free_symbols
300 as_dummy : Rename dummy variables
301 sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable
302 """
303 return [l[0] for l in self.limits if len(l) != 1]
305 @property
306 def free_symbols(self):
307 """
308 This method returns the symbols in the object, excluding those
309 that take on a specific value (i.e. the dummy symbols).
311 Examples
312 ========
314 >>> from sympy import Sum
315 >>> from sympy.abc import x, y
316 >>> Sum(x, (x, y, 1)).free_symbols
317 {y}
318 """
319 # don't test for any special values -- nominal free symbols
320 # should be returned, e.g. don't return set() if the
321 # function is zero -- treat it like an unevaluated expression.
322 function, limits = self.function, self.limits
323 # mask off non-symbol integration variables that have
324 # more than themself as a free symbol
325 reps = {i[0]: i[0] if i[0].free_symbols == {i[0]} else Dummy()
326 for i in self.limits}
327 function = function.xreplace(reps)
328 isyms = function.free_symbols
329 for xab in limits:
330 v = reps[xab[0]]
331 if len(xab) == 1:
332 isyms.add(v)
333 continue
334 # take out the target symbol
335 if v in isyms:
336 isyms.remove(v)
337 # add in the new symbols
338 for i in xab[1:]:
339 isyms.update(i.free_symbols)
340 reps = {v: k for k, v in reps.items()}
341 return {reps.get(_, _) for _ in isyms}
343 @property
344 def is_number(self):
345 """Return True if the Sum has no free symbols, else False."""
346 return not self.free_symbols
348 def _eval_interval(self, x, a, b):
349 limits = [(i if i[0] != x else (x, a, b)) for i in self.limits]
350 integrand = self.function
351 return self.func(integrand, *limits)
353 def _eval_subs(self, old, new):
354 """
355 Perform substitutions over non-dummy variables
356 of an expression with limits. Also, can be used
357 to specify point-evaluation of an abstract antiderivative.
359 Examples
360 ========
362 >>> from sympy import Sum, oo
363 >>> from sympy.abc import s, n
364 >>> Sum(1/n**s, (n, 1, oo)).subs(s, 2)
365 Sum(n**(-2), (n, 1, oo))
367 >>> from sympy import Integral
368 >>> from sympy.abc import x, a
369 >>> Integral(a*x**2, x).subs(x, 4)
370 Integral(a*x**2, (x, 4))
372 See Also
373 ========
375 variables : Lists the integration variables
376 transform : Perform mapping on the dummy variable for integrals
377 change_index : Perform mapping on the sum and product dummy variables
379 """
380 func, limits = self.function, list(self.limits)
382 # If one of the expressions we are replacing is used as a func index
383 # one of two things happens.
384 # - the old variable first appears as a free variable
385 # so we perform all free substitutions before it becomes
386 # a func index.
387 # - the old variable first appears as a func index, in
388 # which case we ignore. See change_index.
390 # Reorder limits to match standard mathematical practice for scoping
391 limits.reverse()
393 if not isinstance(old, Symbol) or \
394 old.free_symbols.intersection(self.free_symbols):
395 sub_into_func = True
396 for i, xab in enumerate(limits):
397 if 1 == len(xab) and old == xab[0]:
398 if new._diff_wrt:
399 xab = (new,)
400 else:
401 xab = (old, old)
402 limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]])
403 if len(xab[0].free_symbols.intersection(old.free_symbols)) != 0:
404 sub_into_func = False
405 break
406 if isinstance(old, (AppliedUndef, UndefinedFunction)):
407 sy2 = set(self.variables).intersection(set(new.atoms(Symbol)))
408 sy1 = set(self.variables).intersection(set(old.args))
409 if not sy2.issubset(sy1):
410 raise ValueError(
411 "substitution cannot create dummy dependencies")
412 sub_into_func = True
413 if sub_into_func:
414 func = func.subs(old, new)
415 else:
416 # old is a Symbol and a dummy variable of some limit
417 for i, xab in enumerate(limits):
418 if len(xab) == 3:
419 limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]])
420 if old == xab[0]:
421 break
422 # simplify redundant limits (x, x) to (x, )
423 for i, xab in enumerate(limits):
424 if len(xab) == 2 and (xab[0] - xab[1]).is_zero:
425 limits[i] = Tuple(xab[0], )
427 # Reorder limits back to representation-form
428 limits.reverse()
430 return self.func(func, *limits)
432 @property
433 def has_finite_limits(self):
434 """
435 Returns True if the limits are known to be finite, either by the
436 explicit bounds, assumptions on the bounds, or assumptions on the
437 variables. False if known to be infinite, based on the bounds.
438 None if not enough information is available to determine.
440 Examples
441 ========
443 >>> from sympy import Sum, Integral, Product, oo, Symbol
444 >>> x = Symbol('x')
445 >>> Sum(x, (x, 1, 8)).has_finite_limits
446 True
448 >>> Integral(x, (x, 1, oo)).has_finite_limits
449 False
451 >>> M = Symbol('M')
452 >>> Sum(x, (x, 1, M)).has_finite_limits
454 >>> N = Symbol('N', integer=True)
455 >>> Product(x, (x, 1, N)).has_finite_limits
456 True
458 See Also
459 ========
461 has_reversed_limits
463 """
465 ret_None = False
466 for lim in self.limits:
467 if len(lim) == 3:
468 if any(l.is_infinite for l in lim[1:]):
469 # Any of the bounds are +/-oo
470 return False
471 elif any(l.is_infinite is None for l in lim[1:]):
472 # Maybe there are assumptions on the variable?
473 if lim[0].is_infinite is None:
474 ret_None = True
475 else:
476 if lim[0].is_infinite is None:
477 ret_None = True
479 if ret_None:
480 return None
481 return True
483 @property
484 def has_reversed_limits(self):
485 """
486 Returns True if the limits are known to be in reversed order, either
487 by the explicit bounds, assumptions on the bounds, or assumptions on the
488 variables. False if known to be in normal order, based on the bounds.
489 None if not enough information is available to determine.
491 Examples
492 ========
494 >>> from sympy import Sum, Integral, Product, oo, Symbol
495 >>> x = Symbol('x')
496 >>> Sum(x, (x, 8, 1)).has_reversed_limits
497 True
499 >>> Sum(x, (x, 1, oo)).has_reversed_limits
500 False
502 >>> M = Symbol('M')
503 >>> Integral(x, (x, 1, M)).has_reversed_limits
505 >>> N = Symbol('N', integer=True, positive=True)
506 >>> Sum(x, (x, 1, N)).has_reversed_limits
507 False
509 >>> Product(x, (x, 2, N)).has_reversed_limits
511 >>> Product(x, (x, 2, N)).subs(N, N + 2).has_reversed_limits
512 False
514 See Also
515 ========
517 sympy.concrete.expr_with_intlimits.ExprWithIntLimits.has_empty_sequence
519 """
520 ret_None = False
521 for lim in self.limits:
522 if len(lim) == 3:
523 var, a, b = lim
524 dif = b - a
525 if dif.is_extended_negative:
526 return True
527 elif dif.is_extended_nonnegative:
528 continue
529 else:
530 ret_None = True
531 else:
532 return None
533 if ret_None:
534 return None
535 return False
538class AddWithLimits(ExprWithLimits):
539 r"""Represents unevaluated oriented additions.
540 Parent class for Integral and Sum.
541 """
543 __slots__ = ()
545 def __new__(cls, function, *symbols, **assumptions):
546 from sympy.concrete.summations import Sum
547 pre = _common_new(cls, function, *symbols,
548 discrete=issubclass(cls, Sum), **assumptions)
549 if isinstance(pre, tuple):
550 function, limits, orientation = pre
551 else:
552 return pre
554 obj = Expr.__new__(cls, **assumptions)
555 arglist = [orientation*function] # orientation not used in ExprWithLimits
556 arglist.extend(limits)
557 obj._args = tuple(arglist)
558 obj.is_commutative = function.is_commutative # limits already checked
560 return obj
562 def _eval_adjoint(self):
563 if all(x.is_real for x in flatten(self.limits)):
564 return self.func(self.function.adjoint(), *self.limits)
565 return None
567 def _eval_conjugate(self):
568 if all(x.is_real for x in flatten(self.limits)):
569 return self.func(self.function.conjugate(), *self.limits)
570 return None
572 def _eval_transpose(self):
573 if all(x.is_real for x in flatten(self.limits)):
574 return self.func(self.function.transpose(), *self.limits)
575 return None
577 def _eval_factor(self, **hints):
578 if 1 == len(self.limits):
579 summand = self.function.factor(**hints)
580 if summand.is_Mul:
581 out = sift(summand.args, lambda w: w.is_commutative \
582 and not set(self.variables) & w.free_symbols)
583 return Mul(*out[True])*self.func(Mul(*out[False]), \
584 *self.limits)
585 else:
586 summand = self.func(self.function, *self.limits[0:-1]).factor()
587 if not summand.has(self.variables[-1]):
588 return self.func(1, [self.limits[-1]]).doit()*summand
589 elif isinstance(summand, Mul):
590 return self.func(summand, self.limits[-1]).factor()
591 return self
593 def _eval_expand_basic(self, **hints):
594 summand = self.function.expand(**hints)
595 force = hints.get('force', False)
596 if (summand.is_Add and (force or summand.is_commutative and
597 self.has_finite_limits is not False)):
598 return Add(*[self.func(i, *self.limits) for i in summand.args])
599 elif isinstance(summand, MatrixBase):
600 return summand.applyfunc(lambda x: self.func(x, *self.limits))
601 elif summand != self.function:
602 return self.func(summand, *self.limits)
603 return self