ZhengyangZhang commited on
Commit
8ba17e6
·
verified ·
1 Parent(s): 6c6ba12

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. lib/python3.12/site-packages/sympy/algebras/__init__.py +3 -0
  2. lib/python3.12/site-packages/sympy/algebras/__pycache__/__init__.cpython-312.pyc +0 -0
  3. lib/python3.12/site-packages/sympy/algebras/__pycache__/quaternion.cpython-312.pyc +0 -0
  4. lib/python3.12/site-packages/sympy/algebras/quaternion.py +1667 -0
  5. lib/python3.12/site-packages/sympy/algebras/tests/__init__.py +0 -0
  6. lib/python3.12/site-packages/sympy/algebras/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  7. lib/python3.12/site-packages/sympy/algebras/tests/__pycache__/test_quaternion.cpython-312.pyc +0 -0
  8. lib/python3.12/site-packages/sympy/algebras/tests/test_quaternion.py +428 -0
  9. lib/python3.12/site-packages/sympy/calculus/__init__.py +25 -0
  10. lib/python3.12/site-packages/sympy/calculus/__pycache__/__init__.cpython-312.pyc +0 -0
  11. lib/python3.12/site-packages/sympy/calculus/__pycache__/accumulationbounds.cpython-312.pyc +0 -0
  12. lib/python3.12/site-packages/sympy/calculus/__pycache__/euler.cpython-312.pyc +0 -0
  13. lib/python3.12/site-packages/sympy/calculus/__pycache__/finite_diff.cpython-312.pyc +0 -0
  14. lib/python3.12/site-packages/sympy/calculus/__pycache__/singularities.cpython-312.pyc +0 -0
  15. lib/python3.12/site-packages/sympy/calculus/__pycache__/util.cpython-312.pyc +0 -0
  16. lib/python3.12/site-packages/sympy/calculus/accumulationbounds.py +804 -0
  17. lib/python3.12/site-packages/sympy/calculus/euler.py +108 -0
  18. lib/python3.12/site-packages/sympy/calculus/finite_diff.py +476 -0
  19. lib/python3.12/site-packages/sympy/calculus/singularities.py +406 -0
  20. lib/python3.12/site-packages/sympy/calculus/tests/__init__.py +0 -0
  21. lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/__init__.cpython-312.pyc +0 -0
  22. lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_accumulationbounds.cpython-312.pyc +0 -0
  23. lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_euler.cpython-312.pyc +0 -0
  24. lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_finite_diff.cpython-312.pyc +0 -0
  25. lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_singularities.cpython-312.pyc +0 -0
  26. lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_util.cpython-312.pyc +0 -0
  27. lib/python3.12/site-packages/sympy/calculus/tests/test_accumulationbounds.py +336 -0
  28. lib/python3.12/site-packages/sympy/calculus/tests/test_euler.py +74 -0
  29. lib/python3.12/site-packages/sympy/calculus/tests/test_finite_diff.py +168 -0
  30. lib/python3.12/site-packages/sympy/calculus/tests/test_singularities.py +122 -0
  31. lib/python3.12/site-packages/sympy/calculus/tests/test_util.py +392 -0
  32. lib/python3.12/site-packages/sympy/calculus/util.py +905 -0
  33. lib/python3.12/site-packages/sympy/concrete/__init__.py +8 -0
  34. lib/python3.12/site-packages/sympy/concrete/__pycache__/__init__.cpython-312.pyc +0 -0
  35. lib/python3.12/site-packages/sympy/concrete/__pycache__/delta.cpython-312.pyc +0 -0
  36. lib/python3.12/site-packages/sympy/concrete/__pycache__/expr_with_intlimits.cpython-312.pyc +0 -0
  37. lib/python3.12/site-packages/sympy/concrete/__pycache__/expr_with_limits.cpython-312.pyc +0 -0
  38. lib/python3.12/site-packages/sympy/concrete/__pycache__/gosper.cpython-312.pyc +0 -0
  39. lib/python3.12/site-packages/sympy/concrete/__pycache__/guess.cpython-312.pyc +0 -0
  40. lib/python3.12/site-packages/sympy/concrete/__pycache__/products.cpython-312.pyc +0 -0
  41. lib/python3.12/site-packages/sympy/concrete/__pycache__/summations.cpython-312.pyc +0 -0
  42. lib/python3.12/site-packages/sympy/concrete/delta.py +330 -0
  43. lib/python3.12/site-packages/sympy/concrete/expr_with_intlimits.py +354 -0
  44. lib/python3.12/site-packages/sympy/concrete/expr_with_limits.py +603 -0
  45. lib/python3.12/site-packages/sympy/concrete/gosper.py +227 -0
  46. lib/python3.12/site-packages/sympy/concrete/guess.py +473 -0
  47. lib/python3.12/site-packages/sympy/concrete/products.py +610 -0
  48. lib/python3.12/site-packages/sympy/concrete/summations.py +1646 -0
  49. lib/python3.12/site-packages/sympy/concrete/tests/__init__.py +0 -0
  50. lib/python3.12/site-packages/sympy/concrete/tests/__pycache__/__init__.cpython-312.pyc +0 -0
lib/python3.12/site-packages/sympy/algebras/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from .quaternion import Quaternion
2
+
3
+ __all__ = ["Quaternion",]
lib/python3.12/site-packages/sympy/algebras/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (268 Bytes). View file
 
lib/python3.12/site-packages/sympy/algebras/__pycache__/quaternion.cpython-312.pyc ADDED
Binary file (62.5 kB). View file
 
lib/python3.12/site-packages/sympy/algebras/quaternion.py ADDED
@@ -0,0 +1,1667 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.numbers import Rational
2
+ from sympy.core.singleton import S
3
+ from sympy.core.relational import is_eq
4
+ from sympy.functions.elementary.complexes import (conjugate, im, re, sign)
5
+ from sympy.functions.elementary.exponential import (exp, log as ln)
6
+ from sympy.functions.elementary.miscellaneous import sqrt
7
+ from sympy.functions.elementary.trigonometric import (acos, asin, atan2)
8
+ from sympy.functions.elementary.trigonometric import (cos, sin)
9
+ from sympy.simplify.trigsimp import trigsimp
10
+ from sympy.integrals.integrals import integrate
11
+ from sympy.matrices.dense import MutableDenseMatrix as Matrix
12
+ from sympy.core.sympify import sympify, _sympify
13
+ from sympy.core.expr import Expr
14
+ from sympy.core.logic import fuzzy_not, fuzzy_or
15
+ from sympy.utilities.misc import as_int
16
+
17
+ from mpmath.libmp.libmpf import prec_to_dps
18
+
19
+
20
+ def _check_norm(elements, norm):
21
+ """validate if input norm is consistent"""
22
+ if norm is not None and norm.is_number:
23
+ if norm.is_positive is False:
24
+ raise ValueError("Input norm must be positive.")
25
+
26
+ numerical = all(i.is_number and i.is_real is True for i in elements)
27
+ if numerical and is_eq(norm**2, sum(i**2 for i in elements)) is False:
28
+ raise ValueError("Incompatible value for norm.")
29
+
30
+
31
+ def _is_extrinsic(seq):
32
+ """validate seq and return True if seq is lowercase and False if uppercase"""
33
+ if type(seq) != str:
34
+ raise ValueError('Expected seq to be a string.')
35
+ if len(seq) != 3:
36
+ raise ValueError("Expected 3 axes, got `{}`.".format(seq))
37
+
38
+ intrinsic = seq.isupper()
39
+ extrinsic = seq.islower()
40
+ if not (intrinsic or extrinsic):
41
+ raise ValueError("seq must either be fully uppercase (for extrinsic "
42
+ "rotations), or fully lowercase, for intrinsic "
43
+ "rotations).")
44
+
45
+ i, j, k = seq.lower()
46
+ if (i == j) or (j == k):
47
+ raise ValueError("Consecutive axes must be different")
48
+
49
+ bad = set(seq) - set('xyzXYZ')
50
+ if bad:
51
+ raise ValueError("Expected axes from `seq` to be from "
52
+ "['x', 'y', 'z'] or ['X', 'Y', 'Z'], "
53
+ "got {}".format(''.join(bad)))
54
+
55
+ return extrinsic
56
+
57
+
58
+ class Quaternion(Expr):
59
+ """Provides basic quaternion operations.
60
+ Quaternion objects can be instantiated as ``Quaternion(a, b, c, d)``
61
+ as in $q = a + bi + cj + dk$.
62
+
63
+ Parameters
64
+ ==========
65
+
66
+ norm : None or number
67
+ Pre-defined quaternion norm. If a value is given, Quaternion.norm
68
+ returns this pre-defined value instead of calculating the norm
69
+
70
+ Examples
71
+ ========
72
+
73
+ >>> from sympy import Quaternion
74
+ >>> q = Quaternion(1, 2, 3, 4)
75
+ >>> q
76
+ 1 + 2*i + 3*j + 4*k
77
+
78
+ Quaternions over complex fields can be defined as:
79
+
80
+ >>> from sympy import Quaternion
81
+ >>> from sympy import symbols, I
82
+ >>> x = symbols('x')
83
+ >>> q1 = Quaternion(x, x**3, x, x**2, real_field = False)
84
+ >>> q2 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
85
+ >>> q1
86
+ x + x**3*i + x*j + x**2*k
87
+ >>> q2
88
+ (3 + 4*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
89
+
90
+ Defining symbolic unit quaternions:
91
+
92
+ >>> from sympy import Quaternion
93
+ >>> from sympy.abc import w, x, y, z
94
+ >>> q = Quaternion(w, x, y, z, norm=1)
95
+ >>> q
96
+ w + x*i + y*j + z*k
97
+ >>> q.norm()
98
+ 1
99
+
100
+ References
101
+ ==========
102
+
103
+ .. [1] https://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/
104
+ .. [2] https://en.wikipedia.org/wiki/Quaternion
105
+
106
+ """
107
+ _op_priority = 11.0
108
+
109
+ is_commutative = False
110
+
111
+ def __new__(cls, a=0, b=0, c=0, d=0, real_field=True, norm=None):
112
+ a, b, c, d = map(sympify, (a, b, c, d))
113
+
114
+ if any(i.is_commutative is False for i in [a, b, c, d]):
115
+ raise ValueError("arguments have to be commutative")
116
+ obj = super().__new__(cls, a, b, c, d)
117
+ obj._real_field = real_field
118
+ obj.set_norm(norm)
119
+ return obj
120
+
121
+ def set_norm(self, norm):
122
+ """Sets norm of an already instantiated quaternion.
123
+
124
+ Parameters
125
+ ==========
126
+
127
+ norm : None or number
128
+ Pre-defined quaternion norm. If a value is given, Quaternion.norm
129
+ returns this pre-defined value instead of calculating the norm
130
+
131
+ Examples
132
+ ========
133
+
134
+ >>> from sympy import Quaternion
135
+ >>> from sympy.abc import a, b, c, d
136
+ >>> q = Quaternion(a, b, c, d)
137
+ >>> q.norm()
138
+ sqrt(a**2 + b**2 + c**2 + d**2)
139
+
140
+ Setting the norm:
141
+
142
+ >>> q.set_norm(1)
143
+ >>> q.norm()
144
+ 1
145
+
146
+ Removing set norm:
147
+
148
+ >>> q.set_norm(None)
149
+ >>> q.norm()
150
+ sqrt(a**2 + b**2 + c**2 + d**2)
151
+
152
+ """
153
+ norm = sympify(norm)
154
+ _check_norm(self.args, norm)
155
+ self._norm = norm
156
+
157
+ @property
158
+ def a(self):
159
+ return self.args[0]
160
+
161
+ @property
162
+ def b(self):
163
+ return self.args[1]
164
+
165
+ @property
166
+ def c(self):
167
+ return self.args[2]
168
+
169
+ @property
170
+ def d(self):
171
+ return self.args[3]
172
+
173
+ @property
174
+ def real_field(self):
175
+ return self._real_field
176
+
177
+ @property
178
+ def product_matrix_left(self):
179
+ r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the
180
+ left. This can be useful when treating quaternion elements as column
181
+ vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d
182
+ are real numbers, the product matrix from the left is:
183
+
184
+ .. math::
185
+
186
+ M = \begin{bmatrix} a &-b &-c &-d \\
187
+ b & a &-d & c \\
188
+ c & d & a &-b \\
189
+ d &-c & b & a \end{bmatrix}
190
+
191
+ Examples
192
+ ========
193
+
194
+ >>> from sympy import Quaternion
195
+ >>> from sympy.abc import a, b, c, d
196
+ >>> q1 = Quaternion(1, 0, 0, 1)
197
+ >>> q2 = Quaternion(a, b, c, d)
198
+ >>> q1.product_matrix_left
199
+ Matrix([
200
+ [1, 0, 0, -1],
201
+ [0, 1, -1, 0],
202
+ [0, 1, 1, 0],
203
+ [1, 0, 0, 1]])
204
+
205
+ >>> q1.product_matrix_left * q2.to_Matrix()
206
+ Matrix([
207
+ [a - d],
208
+ [b - c],
209
+ [b + c],
210
+ [a + d]])
211
+
212
+ This is equivalent to:
213
+
214
+ >>> (q1 * q2).to_Matrix()
215
+ Matrix([
216
+ [a - d],
217
+ [b - c],
218
+ [b + c],
219
+ [a + d]])
220
+ """
221
+ return Matrix([
222
+ [self.a, -self.b, -self.c, -self.d],
223
+ [self.b, self.a, -self.d, self.c],
224
+ [self.c, self.d, self.a, -self.b],
225
+ [self.d, -self.c, self.b, self.a]])
226
+
227
+ @property
228
+ def product_matrix_right(self):
229
+ r"""Returns 4 x 4 Matrix equivalent to a Hamilton product from the
230
+ right. This can be useful when treating quaternion elements as column
231
+ vectors. Given a quaternion $q = a + bi + cj + dk$ where a, b, c and d
232
+ are real numbers, the product matrix from the left is:
233
+
234
+ .. math::
235
+
236
+ M = \begin{bmatrix} a &-b &-c &-d \\
237
+ b & a & d &-c \\
238
+ c &-d & a & b \\
239
+ d & c &-b & a \end{bmatrix}
240
+
241
+
242
+ Examples
243
+ ========
244
+
245
+ >>> from sympy import Quaternion
246
+ >>> from sympy.abc import a, b, c, d
247
+ >>> q1 = Quaternion(a, b, c, d)
248
+ >>> q2 = Quaternion(1, 0, 0, 1)
249
+ >>> q2.product_matrix_right
250
+ Matrix([
251
+ [1, 0, 0, -1],
252
+ [0, 1, 1, 0],
253
+ [0, -1, 1, 0],
254
+ [1, 0, 0, 1]])
255
+
256
+ Note the switched arguments: the matrix represents the quaternion on
257
+ the right, but is still considered as a matrix multiplication from the
258
+ left.
259
+
260
+ >>> q2.product_matrix_right * q1.to_Matrix()
261
+ Matrix([
262
+ [ a - d],
263
+ [ b + c],
264
+ [-b + c],
265
+ [ a + d]])
266
+
267
+ This is equivalent to:
268
+
269
+ >>> (q1 * q2).to_Matrix()
270
+ Matrix([
271
+ [ a - d],
272
+ [ b + c],
273
+ [-b + c],
274
+ [ a + d]])
275
+ """
276
+ return Matrix([
277
+ [self.a, -self.b, -self.c, -self.d],
278
+ [self.b, self.a, self.d, -self.c],
279
+ [self.c, -self.d, self.a, self.b],
280
+ [self.d, self.c, -self.b, self.a]])
281
+
282
+ def to_Matrix(self, vector_only=False):
283
+ """Returns elements of quaternion as a column vector.
284
+ By default, a ``Matrix`` of length 4 is returned, with the real part as the
285
+ first element.
286
+ If ``vector_only`` is ``True``, returns only imaginary part as a Matrix of
287
+ length 3.
288
+
289
+ Parameters
290
+ ==========
291
+
292
+ vector_only : bool
293
+ If True, only imaginary part is returned.
294
+ Default value: False
295
+
296
+ Returns
297
+ =======
298
+
299
+ Matrix
300
+ A column vector constructed by the elements of the quaternion.
301
+
302
+ Examples
303
+ ========
304
+
305
+ >>> from sympy import Quaternion
306
+ >>> from sympy.abc import a, b, c, d
307
+ >>> q = Quaternion(a, b, c, d)
308
+ >>> q
309
+ a + b*i + c*j + d*k
310
+
311
+ >>> q.to_Matrix()
312
+ Matrix([
313
+ [a],
314
+ [b],
315
+ [c],
316
+ [d]])
317
+
318
+
319
+ >>> q.to_Matrix(vector_only=True)
320
+ Matrix([
321
+ [b],
322
+ [c],
323
+ [d]])
324
+
325
+ """
326
+ if vector_only:
327
+ return Matrix(self.args[1:])
328
+ else:
329
+ return Matrix(self.args)
330
+
331
+ @classmethod
332
+ def from_Matrix(cls, elements):
333
+ """Returns quaternion from elements of a column vector`.
334
+ If vector_only is True, returns only imaginary part as a Matrix of
335
+ length 3.
336
+
337
+ Parameters
338
+ ==========
339
+
340
+ elements : Matrix, list or tuple of length 3 or 4. If length is 3,
341
+ assume real part is zero.
342
+ Default value: False
343
+
344
+ Returns
345
+ =======
346
+
347
+ Quaternion
348
+ A quaternion created from the input elements.
349
+
350
+ Examples
351
+ ========
352
+
353
+ >>> from sympy import Quaternion
354
+ >>> from sympy.abc import a, b, c, d
355
+ >>> q = Quaternion.from_Matrix([a, b, c, d])
356
+ >>> q
357
+ a + b*i + c*j + d*k
358
+
359
+ >>> q = Quaternion.from_Matrix([b, c, d])
360
+ >>> q
361
+ 0 + b*i + c*j + d*k
362
+
363
+ """
364
+ length = len(elements)
365
+ if length != 3 and length != 4:
366
+ raise ValueError("Input elements must have length 3 or 4, got {} "
367
+ "elements".format(length))
368
+
369
+ if length == 3:
370
+ return Quaternion(0, *elements)
371
+ else:
372
+ return Quaternion(*elements)
373
+
374
+ @classmethod
375
+ def from_euler(cls, angles, seq):
376
+ """Returns quaternion equivalent to rotation represented by the Euler
377
+ angles, in the sequence defined by ``seq``.
378
+
379
+ Parameters
380
+ ==========
381
+
382
+ angles : list, tuple or Matrix of 3 numbers
383
+ The Euler angles (in radians).
384
+ seq : string of length 3
385
+ Represents the sequence of rotations.
386
+ For extrinsic rotations, seq must be all lowercase and its elements
387
+ must be from the set ``{'x', 'y', 'z'}``
388
+ For intrinsic rotations, seq must be all uppercase and its elements
389
+ must be from the set ``{'X', 'Y', 'Z'}``
390
+
391
+ Returns
392
+ =======
393
+
394
+ Quaternion
395
+ The normalized rotation quaternion calculated from the Euler angles
396
+ in the given sequence.
397
+
398
+ Examples
399
+ ========
400
+
401
+ >>> from sympy import Quaternion
402
+ >>> from sympy import pi
403
+ >>> q = Quaternion.from_euler([pi/2, 0, 0], 'xyz')
404
+ >>> q
405
+ sqrt(2)/2 + sqrt(2)/2*i + 0*j + 0*k
406
+
407
+ >>> q = Quaternion.from_euler([0, pi/2, pi] , 'zyz')
408
+ >>> q
409
+ 0 + (-sqrt(2)/2)*i + 0*j + sqrt(2)/2*k
410
+
411
+ >>> q = Quaternion.from_euler([0, pi/2, pi] , 'ZYZ')
412
+ >>> q
413
+ 0 + sqrt(2)/2*i + 0*j + sqrt(2)/2*k
414
+
415
+ """
416
+
417
+ if len(angles) != 3:
418
+ raise ValueError("3 angles must be given.")
419
+
420
+ extrinsic = _is_extrinsic(seq)
421
+ i, j, k = seq.lower()
422
+
423
+ # get elementary basis vectors
424
+ ei = [1 if n == i else 0 for n in 'xyz']
425
+ ej = [1 if n == j else 0 for n in 'xyz']
426
+ ek = [1 if n == k else 0 for n in 'xyz']
427
+
428
+ # calculate distinct quaternions
429
+ qi = cls.from_axis_angle(ei, angles[0])
430
+ qj = cls.from_axis_angle(ej, angles[1])
431
+ qk = cls.from_axis_angle(ek, angles[2])
432
+
433
+ if extrinsic:
434
+ return trigsimp(qk * qj * qi)
435
+ else:
436
+ return trigsimp(qi * qj * qk)
437
+
438
+ def to_euler(self, seq, angle_addition=True, avoid_square_root=False):
439
+ r"""Returns Euler angles representing same rotation as the quaternion,
440
+ in the sequence given by ``seq``. This implements the method described
441
+ in [1]_.
442
+
443
+ For degenerate cases (gymbal lock cases), the third angle is
444
+ set to zero.
445
+
446
+ Parameters
447
+ ==========
448
+
449
+ seq : string of length 3
450
+ Represents the sequence of rotations.
451
+ For extrinsic rotations, seq must be all lowercase and its elements
452
+ must be from the set ``{'x', 'y', 'z'}``
453
+ For intrinsic rotations, seq must be all uppercase and its elements
454
+ must be from the set ``{'X', 'Y', 'Z'}``
455
+
456
+ angle_addition : bool
457
+ When True, first and third angles are given as an addition and
458
+ subtraction of two simpler ``atan2`` expressions. When False, the
459
+ first and third angles are each given by a single more complicated
460
+ ``atan2`` expression. This equivalent expression is given by:
461
+
462
+ .. math::
463
+
464
+ \operatorname{atan_2} (b,a) \pm \operatorname{atan_2} (d,c) =
465
+ \operatorname{atan_2} (bc\pm ad, ac\mp bd)
466
+
467
+ Default value: True
468
+
469
+ avoid_square_root : bool
470
+ When True, the second angle is calculated with an expression based
471
+ on ``acos``, which is slightly more complicated but avoids a square
472
+ root. When False, second angle is calculated with ``atan2``, which
473
+ is simpler and can be better for numerical reasons (some
474
+ numerical implementations of ``acos`` have problems near zero).
475
+ Default value: False
476
+
477
+
478
+ Returns
479
+ =======
480
+
481
+ Tuple
482
+ The Euler angles calculated from the quaternion
483
+
484
+ Examples
485
+ ========
486
+
487
+ >>> from sympy import Quaternion
488
+ >>> from sympy.abc import a, b, c, d
489
+ >>> euler = Quaternion(a, b, c, d).to_euler('zyz')
490
+ >>> euler
491
+ (-atan2(-b, c) + atan2(d, a),
492
+ 2*atan2(sqrt(b**2 + c**2), sqrt(a**2 + d**2)),
493
+ atan2(-b, c) + atan2(d, a))
494
+
495
+
496
+ References
497
+ ==========
498
+
499
+ .. [1] https://doi.org/10.1371/journal.pone.0276302
500
+
501
+ """
502
+ if self.is_zero_quaternion():
503
+ raise ValueError('Cannot convert a quaternion with norm 0.')
504
+
505
+ angles = [0, 0, 0]
506
+
507
+ extrinsic = _is_extrinsic(seq)
508
+ i, j, k = seq.lower()
509
+
510
+ # get index corresponding to elementary basis vectors
511
+ i = 'xyz'.index(i) + 1
512
+ j = 'xyz'.index(j) + 1
513
+ k = 'xyz'.index(k) + 1
514
+
515
+ if not extrinsic:
516
+ i, k = k, i
517
+
518
+ # check if sequence is symmetric
519
+ symmetric = i == k
520
+ if symmetric:
521
+ k = 6 - i - j
522
+
523
+ # parity of the permutation
524
+ sign = (i - j) * (j - k) * (k - i) // 2
525
+
526
+ # permutate elements
527
+ elements = [self.a, self.b, self.c, self.d]
528
+ a = elements[0]
529
+ b = elements[i]
530
+ c = elements[j]
531
+ d = elements[k] * sign
532
+
533
+ if not symmetric:
534
+ a, b, c, d = a - c, b + d, c + a, d - b
535
+
536
+ if avoid_square_root:
537
+ if symmetric:
538
+ n2 = self.norm()**2
539
+ angles[1] = acos((a * a + b * b - c * c - d * d) / n2)
540
+ else:
541
+ n2 = 2 * self.norm()**2
542
+ angles[1] = asin((c * c + d * d - a * a - b * b) / n2)
543
+ else:
544
+ angles[1] = 2 * atan2(sqrt(c * c + d * d), sqrt(a * a + b * b))
545
+ if not symmetric:
546
+ angles[1] -= S.Pi / 2
547
+
548
+ # Check for singularities in numerical cases
549
+ case = 0
550
+ if is_eq(c, S.Zero) and is_eq(d, S.Zero):
551
+ case = 1
552
+ if is_eq(a, S.Zero) and is_eq(b, S.Zero):
553
+ case = 2
554
+
555
+ if case == 0:
556
+ if angle_addition:
557
+ angles[0] = atan2(b, a) + atan2(d, c)
558
+ angles[2] = atan2(b, a) - atan2(d, c)
559
+ else:
560
+ angles[0] = atan2(b*c + a*d, a*c - b*d)
561
+ angles[2] = atan2(b*c - a*d, a*c + b*d)
562
+
563
+ else: # any degenerate case
564
+ angles[2 * (not extrinsic)] = S.Zero
565
+ if case == 1:
566
+ angles[2 * extrinsic] = 2 * atan2(b, a)
567
+ else:
568
+ angles[2 * extrinsic] = 2 * atan2(d, c)
569
+ angles[2 * extrinsic] *= (-1 if extrinsic else 1)
570
+
571
+ # for Tait-Bryan angles
572
+ if not symmetric:
573
+ angles[0] *= sign
574
+
575
+ if extrinsic:
576
+ return tuple(angles[::-1])
577
+ else:
578
+ return tuple(angles)
579
+
580
+ @classmethod
581
+ def from_axis_angle(cls, vector, angle):
582
+ """Returns a rotation quaternion given the axis and the angle of rotation.
583
+
584
+ Parameters
585
+ ==========
586
+
587
+ vector : tuple of three numbers
588
+ The vector representation of the given axis.
589
+ angle : number
590
+ The angle by which axis is rotated (in radians).
591
+
592
+ Returns
593
+ =======
594
+
595
+ Quaternion
596
+ The normalized rotation quaternion calculated from the given axis and the angle of rotation.
597
+
598
+ Examples
599
+ ========
600
+
601
+ >>> from sympy import Quaternion
602
+ >>> from sympy import pi, sqrt
603
+ >>> q = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), 2*pi/3)
604
+ >>> q
605
+ 1/2 + 1/2*i + 1/2*j + 1/2*k
606
+
607
+ """
608
+ (x, y, z) = vector
609
+ norm = sqrt(x**2 + y**2 + z**2)
610
+ (x, y, z) = (x / norm, y / norm, z / norm)
611
+ s = sin(angle * S.Half)
612
+ a = cos(angle * S.Half)
613
+ b = x * s
614
+ c = y * s
615
+ d = z * s
616
+
617
+ # note that this quaternion is already normalized by construction:
618
+ # c^2 + (s*x)^2 + (s*y)^2 + (s*z)^2 = c^2 + s^2*(x^2 + y^2 + z^2) = c^2 + s^2 * 1 = c^2 + s^2 = 1
619
+ # so, what we return is a normalized quaternion
620
+
621
+ return cls(a, b, c, d)
622
+
623
+ @classmethod
624
+ def from_rotation_matrix(cls, M):
625
+ """Returns the equivalent quaternion of a matrix. The quaternion will be normalized
626
+ only if the matrix is special orthogonal (orthogonal and det(M) = 1).
627
+
628
+ Parameters
629
+ ==========
630
+
631
+ M : Matrix
632
+ Input matrix to be converted to equivalent quaternion. M must be special
633
+ orthogonal (orthogonal and det(M) = 1) for the quaternion to be normalized.
634
+
635
+ Returns
636
+ =======
637
+
638
+ Quaternion
639
+ The quaternion equivalent to given matrix.
640
+
641
+ Examples
642
+ ========
643
+
644
+ >>> from sympy import Quaternion
645
+ >>> from sympy import Matrix, symbols, cos, sin, trigsimp
646
+ >>> x = symbols('x')
647
+ >>> M = Matrix([[cos(x), -sin(x), 0], [sin(x), cos(x), 0], [0, 0, 1]])
648
+ >>> q = trigsimp(Quaternion.from_rotation_matrix(M))
649
+ >>> q
650
+ sqrt(2)*sqrt(cos(x) + 1)/2 + 0*i + 0*j + sqrt(2 - 2*cos(x))*sign(sin(x))/2*k
651
+
652
+ """
653
+
654
+ absQ = M.det()**Rational(1, 3)
655
+
656
+ a = sqrt(absQ + M[0, 0] + M[1, 1] + M[2, 2]) / 2
657
+ b = sqrt(absQ + M[0, 0] - M[1, 1] - M[2, 2]) / 2
658
+ c = sqrt(absQ - M[0, 0] + M[1, 1] - M[2, 2]) / 2
659
+ d = sqrt(absQ - M[0, 0] - M[1, 1] + M[2, 2]) / 2
660
+
661
+ b = b * sign(M[2, 1] - M[1, 2])
662
+ c = c * sign(M[0, 2] - M[2, 0])
663
+ d = d * sign(M[1, 0] - M[0, 1])
664
+
665
+ return Quaternion(a, b, c, d)
666
+
667
+ def __add__(self, other):
668
+ return self.add(other)
669
+
670
+ def __radd__(self, other):
671
+ return self.add(other)
672
+
673
+ def __sub__(self, other):
674
+ return self.add(other*-1)
675
+
676
+ def __mul__(self, other):
677
+ return self._generic_mul(self, _sympify(other))
678
+
679
+ def __rmul__(self, other):
680
+ return self._generic_mul(_sympify(other), self)
681
+
682
+ def __pow__(self, p):
683
+ return self.pow(p)
684
+
685
+ def __neg__(self):
686
+ return Quaternion(-self.a, -self.b, -self.c, -self.d)
687
+
688
+ def __truediv__(self, other):
689
+ return self * sympify(other)**-1
690
+
691
+ def __rtruediv__(self, other):
692
+ return sympify(other) * self**-1
693
+
694
+ def _eval_Integral(self, *args):
695
+ return self.integrate(*args)
696
+
697
+ def diff(self, *symbols, **kwargs):
698
+ kwargs.setdefault('evaluate', True)
699
+ return self.func(*[a.diff(*symbols, **kwargs) for a in self.args])
700
+
701
+ def add(self, other):
702
+ """Adds quaternions.
703
+
704
+ Parameters
705
+ ==========
706
+
707
+ other : Quaternion
708
+ The quaternion to add to current (self) quaternion.
709
+
710
+ Returns
711
+ =======
712
+
713
+ Quaternion
714
+ The resultant quaternion after adding self to other
715
+
716
+ Examples
717
+ ========
718
+
719
+ >>> from sympy import Quaternion
720
+ >>> from sympy import symbols
721
+ >>> q1 = Quaternion(1, 2, 3, 4)
722
+ >>> q2 = Quaternion(5, 6, 7, 8)
723
+ >>> q1.add(q2)
724
+ 6 + 8*i + 10*j + 12*k
725
+ >>> q1 + 5
726
+ 6 + 2*i + 3*j + 4*k
727
+ >>> x = symbols('x', real = True)
728
+ >>> q1.add(x)
729
+ (x + 1) + 2*i + 3*j + 4*k
730
+
731
+ Quaternions over complex fields :
732
+
733
+ >>> from sympy import Quaternion
734
+ >>> from sympy import I
735
+ >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
736
+ >>> q3.add(2 + 3*I)
737
+ (5 + 7*I) + (2 + 5*I)*i + 0*j + (7 + 8*I)*k
738
+
739
+ """
740
+ q1 = self
741
+ q2 = sympify(other)
742
+
743
+ # If q2 is a number or a SymPy expression instead of a quaternion
744
+ if not isinstance(q2, Quaternion):
745
+ if q1.real_field and q2.is_complex:
746
+ return Quaternion(re(q2) + q1.a, im(q2) + q1.b, q1.c, q1.d)
747
+ elif q2.is_commutative:
748
+ return Quaternion(q1.a + q2, q1.b, q1.c, q1.d)
749
+ else:
750
+ raise ValueError("Only commutative expressions can be added with a Quaternion.")
751
+
752
+ return Quaternion(q1.a + q2.a, q1.b + q2.b, q1.c + q2.c, q1.d
753
+ + q2.d)
754
+
755
+ def mul(self, other):
756
+ """Multiplies quaternions.
757
+
758
+ Parameters
759
+ ==========
760
+
761
+ other : Quaternion or symbol
762
+ The quaternion to multiply to current (self) quaternion.
763
+
764
+ Returns
765
+ =======
766
+
767
+ Quaternion
768
+ The resultant quaternion after multiplying self with other
769
+
770
+ Examples
771
+ ========
772
+
773
+ >>> from sympy import Quaternion
774
+ >>> from sympy import symbols
775
+ >>> q1 = Quaternion(1, 2, 3, 4)
776
+ >>> q2 = Quaternion(5, 6, 7, 8)
777
+ >>> q1.mul(q2)
778
+ (-60) + 12*i + 30*j + 24*k
779
+ >>> q1.mul(2)
780
+ 2 + 4*i + 6*j + 8*k
781
+ >>> x = symbols('x', real = True)
782
+ >>> q1.mul(x)
783
+ x + 2*x*i + 3*x*j + 4*x*k
784
+
785
+ Quaternions over complex fields :
786
+
787
+ >>> from sympy import Quaternion
788
+ >>> from sympy import I
789
+ >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
790
+ >>> q3.mul(2 + 3*I)
791
+ (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
792
+
793
+ """
794
+ return self._generic_mul(self, _sympify(other))
795
+
796
+ @staticmethod
797
+ def _generic_mul(q1, q2):
798
+ """Generic multiplication.
799
+
800
+ Parameters
801
+ ==========
802
+
803
+ q1 : Quaternion or symbol
804
+ q2 : Quaternion or symbol
805
+
806
+ It is important to note that if neither q1 nor q2 is a Quaternion,
807
+ this function simply returns q1 * q2.
808
+
809
+ Returns
810
+ =======
811
+
812
+ Quaternion
813
+ The resultant quaternion after multiplying q1 and q2
814
+
815
+ Examples
816
+ ========
817
+
818
+ >>> from sympy import Quaternion
819
+ >>> from sympy import Symbol, S
820
+ >>> q1 = Quaternion(1, 2, 3, 4)
821
+ >>> q2 = Quaternion(5, 6, 7, 8)
822
+ >>> Quaternion._generic_mul(q1, q2)
823
+ (-60) + 12*i + 30*j + 24*k
824
+ >>> Quaternion._generic_mul(q1, S(2))
825
+ 2 + 4*i + 6*j + 8*k
826
+ >>> x = Symbol('x', real = True)
827
+ >>> Quaternion._generic_mul(q1, x)
828
+ x + 2*x*i + 3*x*j + 4*x*k
829
+
830
+ Quaternions over complex fields :
831
+
832
+ >>> from sympy import I
833
+ >>> q3 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
834
+ >>> Quaternion._generic_mul(q3, 2 + 3*I)
835
+ (2 + 3*I)*(3 + 4*I) + (2 + 3*I)*(2 + 5*I)*i + 0*j + (2 + 3*I)*(7 + 8*I)*k
836
+
837
+ """
838
+ # None is a Quaternion:
839
+ if not isinstance(q1, Quaternion) and not isinstance(q2, Quaternion):
840
+ return q1 * q2
841
+
842
+ # If q1 is a number or a SymPy expression instead of a quaternion
843
+ if not isinstance(q1, Quaternion):
844
+ if q2.real_field and q1.is_complex:
845
+ return Quaternion(re(q1), im(q1), 0, 0) * q2
846
+ elif q1.is_commutative:
847
+ return Quaternion(q1 * q2.a, q1 * q2.b, q1 * q2.c, q1 * q2.d)
848
+ else:
849
+ raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
850
+
851
+ # If q2 is a number or a SymPy expression instead of a quaternion
852
+ if not isinstance(q2, Quaternion):
853
+ if q1.real_field and q2.is_complex:
854
+ return q1 * Quaternion(re(q2), im(q2), 0, 0)
855
+ elif q2.is_commutative:
856
+ return Quaternion(q2 * q1.a, q2 * q1.b, q2 * q1.c, q2 * q1.d)
857
+ else:
858
+ raise ValueError("Only commutative expressions can be multiplied with a Quaternion.")
859
+
860
+ # If any of the quaternions has a fixed norm, pre-compute norm
861
+ if q1._norm is None and q2._norm is None:
862
+ norm = None
863
+ else:
864
+ norm = q1.norm() * q2.norm()
865
+
866
+ return Quaternion(-q1.b*q2.b - q1.c*q2.c - q1.d*q2.d + q1.a*q2.a,
867
+ q1.b*q2.a + q1.c*q2.d - q1.d*q2.c + q1.a*q2.b,
868
+ -q1.b*q2.d + q1.c*q2.a + q1.d*q2.b + q1.a*q2.c,
869
+ q1.b*q2.c - q1.c*q2.b + q1.d*q2.a + q1.a * q2.d,
870
+ norm=norm)
871
+
872
+ def _eval_conjugate(self):
873
+ """Returns the conjugate of the quaternion."""
874
+ q = self
875
+ return Quaternion(q.a, -q.b, -q.c, -q.d, norm=q._norm)
876
+
877
+ def norm(self):
878
+ """Returns the norm of the quaternion."""
879
+ if self._norm is None: # check if norm is pre-defined
880
+ q = self
881
+ # trigsimp is used to simplify sin(x)^2 + cos(x)^2 (these terms
882
+ # arise when from_axis_angle is used).
883
+ return sqrt(trigsimp(q.a**2 + q.b**2 + q.c**2 + q.d**2))
884
+
885
+ return self._norm
886
+
887
+ def normalize(self):
888
+ """Returns the normalized form of the quaternion."""
889
+ q = self
890
+ return q * (1/q.norm())
891
+
892
+ def inverse(self):
893
+ """Returns the inverse of the quaternion."""
894
+ q = self
895
+ if not q.norm():
896
+ raise ValueError("Cannot compute inverse for a quaternion with zero norm")
897
+ return conjugate(q) * (1/q.norm()**2)
898
+
899
+ def pow(self, p):
900
+ """Finds the pth power of the quaternion.
901
+
902
+ Parameters
903
+ ==========
904
+
905
+ p : int
906
+ Power to be applied on quaternion.
907
+
908
+ Returns
909
+ =======
910
+
911
+ Quaternion
912
+ Returns the p-th power of the current quaternion.
913
+ Returns the inverse if p = -1.
914
+
915
+ Examples
916
+ ========
917
+
918
+ >>> from sympy import Quaternion
919
+ >>> q = Quaternion(1, 2, 3, 4)
920
+ >>> q.pow(4)
921
+ 668 + (-224)*i + (-336)*j + (-448)*k
922
+
923
+ """
924
+ try:
925
+ q, p = self, as_int(p)
926
+ except ValueError:
927
+ return NotImplemented
928
+
929
+ if p < 0:
930
+ q, p = q.inverse(), -p
931
+
932
+ if p == 1:
933
+ return q
934
+
935
+ res = Quaternion(1, 0, 0, 0)
936
+ while p > 0:
937
+ if p & 1:
938
+ res *= q
939
+ q *= q
940
+ p >>= 1
941
+
942
+ return res
943
+
944
+ def exp(self):
945
+ """Returns the exponential of $q$, given by $e^q$.
946
+
947
+ Returns
948
+ =======
949
+
950
+ Quaternion
951
+ The exponential of the quaternion.
952
+
953
+ Examples
954
+ ========
955
+
956
+ >>> from sympy import Quaternion
957
+ >>> q = Quaternion(1, 2, 3, 4)
958
+ >>> q.exp()
959
+ E*cos(sqrt(29))
960
+ + 2*sqrt(29)*E*sin(sqrt(29))/29*i
961
+ + 3*sqrt(29)*E*sin(sqrt(29))/29*j
962
+ + 4*sqrt(29)*E*sin(sqrt(29))/29*k
963
+
964
+ """
965
+ # exp(q) = e^a(cos||v|| + v/||v||*sin||v||)
966
+ q = self
967
+ vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
968
+ a = exp(q.a) * cos(vector_norm)
969
+ b = exp(q.a) * sin(vector_norm) * q.b / vector_norm
970
+ c = exp(q.a) * sin(vector_norm) * q.c / vector_norm
971
+ d = exp(q.a) * sin(vector_norm) * q.d / vector_norm
972
+
973
+ return Quaternion(a, b, c, d)
974
+
975
+ def log(self):
976
+ r"""Returns the logarithm of the quaternion, given by $\log q$.
977
+
978
+ Examples
979
+ ========
980
+
981
+ >>> from sympy import Quaternion
982
+ >>> q = Quaternion(1, 2, 3, 4)
983
+ >>> q.log()
984
+ log(sqrt(30))
985
+ + 2*sqrt(29)*acos(sqrt(30)/30)/29*i
986
+ + 3*sqrt(29)*acos(sqrt(30)/30)/29*j
987
+ + 4*sqrt(29)*acos(sqrt(30)/30)/29*k
988
+
989
+ """
990
+ # log(q) = log||q|| + v/||v||*arccos(a/||q||)
991
+ q = self
992
+ vector_norm = sqrt(q.b**2 + q.c**2 + q.d**2)
993
+ q_norm = q.norm()
994
+ a = ln(q_norm)
995
+ b = q.b * acos(q.a / q_norm) / vector_norm
996
+ c = q.c * acos(q.a / q_norm) / vector_norm
997
+ d = q.d * acos(q.a / q_norm) / vector_norm
998
+
999
+ return Quaternion(a, b, c, d)
1000
+
1001
+ def _eval_subs(self, *args):
1002
+ elements = [i.subs(*args) for i in self.args]
1003
+ norm = self._norm
1004
+ if norm is not None:
1005
+ norm = norm.subs(*args)
1006
+ _check_norm(elements, norm)
1007
+ return Quaternion(*elements, norm=norm)
1008
+
1009
+ def _eval_evalf(self, prec):
1010
+ """Returns the floating point approximations (decimal numbers) of the quaternion.
1011
+
1012
+ Returns
1013
+ =======
1014
+
1015
+ Quaternion
1016
+ Floating point approximations of quaternion(self)
1017
+
1018
+ Examples
1019
+ ========
1020
+
1021
+ >>> from sympy import Quaternion
1022
+ >>> from sympy import sqrt
1023
+ >>> q = Quaternion(1/sqrt(1), 1/sqrt(2), 1/sqrt(3), 1/sqrt(4))
1024
+ >>> q.evalf()
1025
+ 1.00000000000000
1026
+ + 0.707106781186547*i
1027
+ + 0.577350269189626*j
1028
+ + 0.500000000000000*k
1029
+
1030
+ """
1031
+ nprec = prec_to_dps(prec)
1032
+ return Quaternion(*[arg.evalf(n=nprec) for arg in self.args])
1033
+
1034
+ def pow_cos_sin(self, p):
1035
+ """Computes the pth power in the cos-sin form.
1036
+
1037
+ Parameters
1038
+ ==========
1039
+
1040
+ p : int
1041
+ Power to be applied on quaternion.
1042
+
1043
+ Returns
1044
+ =======
1045
+
1046
+ Quaternion
1047
+ The p-th power in the cos-sin form.
1048
+
1049
+ Examples
1050
+ ========
1051
+
1052
+ >>> from sympy import Quaternion
1053
+ >>> q = Quaternion(1, 2, 3, 4)
1054
+ >>> q.pow_cos_sin(4)
1055
+ 900*cos(4*acos(sqrt(30)/30))
1056
+ + 1800*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*i
1057
+ + 2700*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*j
1058
+ + 3600*sqrt(29)*sin(4*acos(sqrt(30)/30))/29*k
1059
+
1060
+ """
1061
+ # q = ||q||*(cos(a) + u*sin(a))
1062
+ # q^p = ||q||^p * (cos(p*a) + u*sin(p*a))
1063
+
1064
+ q = self
1065
+ (v, angle) = q.to_axis_angle()
1066
+ q2 = Quaternion.from_axis_angle(v, p * angle)
1067
+ return q2 * (q.norm()**p)
1068
+
1069
+ def integrate(self, *args):
1070
+ """Computes integration of quaternion.
1071
+
1072
+ Returns
1073
+ =======
1074
+
1075
+ Quaternion
1076
+ Integration of the quaternion(self) with the given variable.
1077
+
1078
+ Examples
1079
+ ========
1080
+
1081
+ Indefinite Integral of quaternion :
1082
+
1083
+ >>> from sympy import Quaternion
1084
+ >>> from sympy.abc import x
1085
+ >>> q = Quaternion(1, 2, 3, 4)
1086
+ >>> q.integrate(x)
1087
+ x + 2*x*i + 3*x*j + 4*x*k
1088
+
1089
+ Definite integral of quaternion :
1090
+
1091
+ >>> from sympy import Quaternion
1092
+ >>> from sympy.abc import x
1093
+ >>> q = Quaternion(1, 2, 3, 4)
1094
+ >>> q.integrate((x, 1, 5))
1095
+ 4 + 8*i + 12*j + 16*k
1096
+
1097
+ """
1098
+ # TODO: is this expression correct?
1099
+ return Quaternion(integrate(self.a, *args), integrate(self.b, *args),
1100
+ integrate(self.c, *args), integrate(self.d, *args))
1101
+
1102
+ @staticmethod
1103
+ def rotate_point(pin, r):
1104
+ """Returns the coordinates of the point pin (a 3 tuple) after rotation.
1105
+
1106
+ Parameters
1107
+ ==========
1108
+
1109
+ pin : tuple
1110
+ A 3-element tuple of coordinates of a point which needs to be
1111
+ rotated.
1112
+ r : Quaternion or tuple
1113
+ Axis and angle of rotation.
1114
+
1115
+ It's important to note that when r is a tuple, it must be of the form
1116
+ (axis, angle)
1117
+
1118
+ Returns
1119
+ =======
1120
+
1121
+ tuple
1122
+ The coordinates of the point after rotation.
1123
+
1124
+ Examples
1125
+ ========
1126
+
1127
+ >>> from sympy import Quaternion
1128
+ >>> from sympy import symbols, trigsimp, cos, sin
1129
+ >>> x = symbols('x')
1130
+ >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
1131
+ >>> trigsimp(Quaternion.rotate_point((1, 1, 1), q))
1132
+ (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
1133
+ >>> (axis, angle) = q.to_axis_angle()
1134
+ >>> trigsimp(Quaternion.rotate_point((1, 1, 1), (axis, angle)))
1135
+ (sqrt(2)*cos(x + pi/4), sqrt(2)*sin(x + pi/4), 1)
1136
+
1137
+ """
1138
+ if isinstance(r, tuple):
1139
+ # if r is of the form (vector, angle)
1140
+ q = Quaternion.from_axis_angle(r[0], r[1])
1141
+ else:
1142
+ # if r is a quaternion
1143
+ q = r.normalize()
1144
+ pout = q * Quaternion(0, pin[0], pin[1], pin[2]) * conjugate(q)
1145
+ return (pout.b, pout.c, pout.d)
1146
+
1147
+ def to_axis_angle(self):
1148
+ """Returns the axis and angle of rotation of a quaternion.
1149
+
1150
+ Returns
1151
+ =======
1152
+
1153
+ tuple
1154
+ Tuple of (axis, angle)
1155
+
1156
+ Examples
1157
+ ========
1158
+
1159
+ >>> from sympy import Quaternion
1160
+ >>> q = Quaternion(1, 1, 1, 1)
1161
+ >>> (axis, angle) = q.to_axis_angle()
1162
+ >>> axis
1163
+ (sqrt(3)/3, sqrt(3)/3, sqrt(3)/3)
1164
+ >>> angle
1165
+ 2*pi/3
1166
+
1167
+ """
1168
+ q = self
1169
+ if q.a.is_negative:
1170
+ q = q * -1
1171
+
1172
+ q = q.normalize()
1173
+ angle = trigsimp(2 * acos(q.a))
1174
+
1175
+ # Since quaternion is normalised, q.a is less than 1.
1176
+ s = sqrt(1 - q.a*q.a)
1177
+
1178
+ x = trigsimp(q.b / s)
1179
+ y = trigsimp(q.c / s)
1180
+ z = trigsimp(q.d / s)
1181
+
1182
+ v = (x, y, z)
1183
+ t = (v, angle)
1184
+
1185
+ return t
1186
+
1187
+ def to_rotation_matrix(self, v=None, homogeneous=True):
1188
+ """Returns the equivalent rotation transformation matrix of the quaternion
1189
+ which represents rotation about the origin if ``v`` is not passed.
1190
+
1191
+ Parameters
1192
+ ==========
1193
+
1194
+ v : tuple or None
1195
+ Default value: None
1196
+ homogeneous : bool
1197
+ When True, gives an expression that may be more efficient for
1198
+ symbolic calculations but less so for direct evaluation. Both
1199
+ formulas are mathematically equivalent.
1200
+ Default value: True
1201
+
1202
+ Returns
1203
+ =======
1204
+
1205
+ tuple
1206
+ Returns the equivalent rotation transformation matrix of the quaternion
1207
+ which represents rotation about the origin if v is not passed.
1208
+
1209
+ Examples
1210
+ ========
1211
+
1212
+ >>> from sympy import Quaternion
1213
+ >>> from sympy import symbols, trigsimp, cos, sin
1214
+ >>> x = symbols('x')
1215
+ >>> q = Quaternion(cos(x/2), 0, 0, sin(x/2))
1216
+ >>> trigsimp(q.to_rotation_matrix())
1217
+ Matrix([
1218
+ [cos(x), -sin(x), 0],
1219
+ [sin(x), cos(x), 0],
1220
+ [ 0, 0, 1]])
1221
+
1222
+ Generates a 4x4 transformation matrix (used for rotation about a point
1223
+ other than the origin) if the point(v) is passed as an argument.
1224
+ """
1225
+
1226
+ q = self
1227
+ s = q.norm()**-2
1228
+
1229
+ # diagonal elements are different according to parameter normal
1230
+ if homogeneous:
1231
+ m00 = s*(q.a**2 + q.b**2 - q.c**2 - q.d**2)
1232
+ m11 = s*(q.a**2 - q.b**2 + q.c**2 - q.d**2)
1233
+ m22 = s*(q.a**2 - q.b**2 - q.c**2 + q.d**2)
1234
+ else:
1235
+ m00 = 1 - 2*s*(q.c**2 + q.d**2)
1236
+ m11 = 1 - 2*s*(q.b**2 + q.d**2)
1237
+ m22 = 1 - 2*s*(q.b**2 + q.c**2)
1238
+
1239
+ m01 = 2*s*(q.b*q.c - q.d*q.a)
1240
+ m02 = 2*s*(q.b*q.d + q.c*q.a)
1241
+
1242
+ m10 = 2*s*(q.b*q.c + q.d*q.a)
1243
+ m12 = 2*s*(q.c*q.d - q.b*q.a)
1244
+
1245
+ m20 = 2*s*(q.b*q.d - q.c*q.a)
1246
+ m21 = 2*s*(q.c*q.d + q.b*q.a)
1247
+
1248
+ if not v:
1249
+ return Matrix([[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]])
1250
+
1251
+ else:
1252
+ (x, y, z) = v
1253
+
1254
+ m03 = x - x*m00 - y*m01 - z*m02
1255
+ m13 = y - x*m10 - y*m11 - z*m12
1256
+ m23 = z - x*m20 - y*m21 - z*m22
1257
+ m30 = m31 = m32 = 0
1258
+ m33 = 1
1259
+
1260
+ return Matrix([[m00, m01, m02, m03], [m10, m11, m12, m13],
1261
+ [m20, m21, m22, m23], [m30, m31, m32, m33]])
1262
+
1263
+ def scalar_part(self):
1264
+ r"""Returns scalar part($\mathbf{S}(q)$) of the quaternion q.
1265
+
1266
+ Explanation
1267
+ ===========
1268
+
1269
+ Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{S}(q) = a$.
1270
+
1271
+ Examples
1272
+ ========
1273
+
1274
+ >>> from sympy.algebras.quaternion import Quaternion
1275
+ >>> q = Quaternion(4, 8, 13, 12)
1276
+ >>> q.scalar_part()
1277
+ 4
1278
+
1279
+ """
1280
+
1281
+ return self.a
1282
+
1283
+ def vector_part(self):
1284
+ r"""
1285
+ Returns $\mathbf{V}(q)$, the vector part of the quaternion $q$.
1286
+
1287
+ Explanation
1288
+ ===========
1289
+
1290
+ Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{V}(q) = bi + cj + dk$.
1291
+
1292
+ Examples
1293
+ ========
1294
+
1295
+ >>> from sympy.algebras.quaternion import Quaternion
1296
+ >>> q = Quaternion(1, 1, 1, 1)
1297
+ >>> q.vector_part()
1298
+ 0 + 1*i + 1*j + 1*k
1299
+
1300
+ >>> q = Quaternion(4, 8, 13, 12)
1301
+ >>> q.vector_part()
1302
+ 0 + 8*i + 13*j + 12*k
1303
+
1304
+ """
1305
+
1306
+ return Quaternion(0, self.b, self.c, self.d)
1307
+
1308
+ def axis(self):
1309
+ r"""
1310
+ Returns $\mathbf{Ax}(q)$, the axis of the quaternion $q$.
1311
+
1312
+ Explanation
1313
+ ===========
1314
+
1315
+ Given a quaternion $q = a + bi + cj + dk$, returns $\mathbf{Ax}(q)$ i.e., the versor of the vector part of that quaternion
1316
+ equal to $\mathbf{U}[\mathbf{V}(q)]$.
1317
+ The axis is always an imaginary unit with square equal to $-1 + 0i + 0j + 0k$.
1318
+
1319
+ Examples
1320
+ ========
1321
+
1322
+ >>> from sympy.algebras.quaternion import Quaternion
1323
+ >>> q = Quaternion(1, 1, 1, 1)
1324
+ >>> q.axis()
1325
+ 0 + sqrt(3)/3*i + sqrt(3)/3*j + sqrt(3)/3*k
1326
+
1327
+ See Also
1328
+ ========
1329
+
1330
+ vector_part
1331
+
1332
+ """
1333
+ axis = self.vector_part().normalize()
1334
+
1335
+ return Quaternion(0, axis.b, axis.c, axis.d)
1336
+
1337
+ def is_pure(self):
1338
+ """
1339
+ Returns true if the quaternion is pure, false if the quaternion is not pure
1340
+ or returns none if it is unknown.
1341
+
1342
+ Explanation
1343
+ ===========
1344
+
1345
+ A pure quaternion (also a vector quaternion) is a quaternion with scalar
1346
+ part equal to 0.
1347
+
1348
+ Examples
1349
+ ========
1350
+
1351
+ >>> from sympy.algebras.quaternion import Quaternion
1352
+ >>> q = Quaternion(0, 8, 13, 12)
1353
+ >>> q.is_pure()
1354
+ True
1355
+
1356
+ See Also
1357
+ ========
1358
+ scalar_part
1359
+
1360
+ """
1361
+
1362
+ return self.a.is_zero
1363
+
1364
+ def is_zero_quaternion(self):
1365
+ """
1366
+ Returns true if the quaternion is a zero quaternion or false if it is not a zero quaternion
1367
+ and None if the value is unknown.
1368
+
1369
+ Explanation
1370
+ ===========
1371
+
1372
+ A zero quaternion is a quaternion with both scalar part and
1373
+ vector part equal to 0.
1374
+
1375
+ Examples
1376
+ ========
1377
+
1378
+ >>> from sympy.algebras.quaternion import Quaternion
1379
+ >>> q = Quaternion(1, 0, 0, 0)
1380
+ >>> q.is_zero_quaternion()
1381
+ False
1382
+
1383
+ >>> q = Quaternion(0, 0, 0, 0)
1384
+ >>> q.is_zero_quaternion()
1385
+ True
1386
+
1387
+ See Also
1388
+ ========
1389
+ scalar_part
1390
+ vector_part
1391
+
1392
+ """
1393
+
1394
+ return self.norm().is_zero
1395
+
1396
+ def angle(self):
1397
+ r"""
1398
+ Returns the angle of the quaternion measured in the real-axis plane.
1399
+
1400
+ Explanation
1401
+ ===========
1402
+
1403
+ Given a quaternion $q = a + bi + cj + dk$ where $a$, $b$, $c$ and $d$
1404
+ are real numbers, returns the angle of the quaternion given by
1405
+
1406
+ .. math::
1407
+ \theta := 2 \operatorname{atan_2}\left(\sqrt{b^2 + c^2 + d^2}, {a}\right)
1408
+
1409
+ Examples
1410
+ ========
1411
+
1412
+ >>> from sympy.algebras.quaternion import Quaternion
1413
+ >>> q = Quaternion(1, 4, 4, 4)
1414
+ >>> q.angle()
1415
+ 2*atan(4*sqrt(3))
1416
+
1417
+ """
1418
+
1419
+ return 2 * atan2(self.vector_part().norm(), self.scalar_part())
1420
+
1421
+
1422
+ def arc_coplanar(self, other):
1423
+ """
1424
+ Returns True if the transformation arcs represented by the input quaternions happen in the same plane.
1425
+
1426
+ Explanation
1427
+ ===========
1428
+
1429
+ Two quaternions are said to be coplanar (in this arc sense) when their axes are parallel.
1430
+ The plane of a quaternion is the one normal to its axis.
1431
+
1432
+ Parameters
1433
+ ==========
1434
+
1435
+ other : a Quaternion
1436
+
1437
+ Returns
1438
+ =======
1439
+
1440
+ True : if the planes of the two quaternions are the same, apart from its orientation/sign.
1441
+ False : if the planes of the two quaternions are not the same, apart from its orientation/sign.
1442
+ None : if plane of either of the quaternion is unknown.
1443
+
1444
+ Examples
1445
+ ========
1446
+
1447
+ >>> from sympy.algebras.quaternion import Quaternion
1448
+ >>> q1 = Quaternion(1, 4, 4, 4)
1449
+ >>> q2 = Quaternion(3, 8, 8, 8)
1450
+ >>> Quaternion.arc_coplanar(q1, q2)
1451
+ True
1452
+
1453
+ >>> q1 = Quaternion(2, 8, 13, 12)
1454
+ >>> Quaternion.arc_coplanar(q1, q2)
1455
+ False
1456
+
1457
+ See Also
1458
+ ========
1459
+
1460
+ vector_coplanar
1461
+ is_pure
1462
+
1463
+ """
1464
+ if (self.is_zero_quaternion()) or (other.is_zero_quaternion()):
1465
+ raise ValueError('Neither of the given quaternions can be 0')
1466
+
1467
+ return fuzzy_or([(self.axis() - other.axis()).is_zero_quaternion(), (self.axis() + other.axis()).is_zero_quaternion()])
1468
+
1469
+ @classmethod
1470
+ def vector_coplanar(cls, q1, q2, q3):
1471
+ r"""
1472
+ Returns True if the axis of the pure quaternions seen as 3D vectors
1473
+ ``q1``, ``q2``, and ``q3`` are coplanar.
1474
+
1475
+ Explanation
1476
+ ===========
1477
+
1478
+ Three pure quaternions are vector coplanar if the quaternions seen as 3D vectors are coplanar.
1479
+
1480
+ Parameters
1481
+ ==========
1482
+
1483
+ q1
1484
+ A pure Quaternion.
1485
+ q2
1486
+ A pure Quaternion.
1487
+ q3
1488
+ A pure Quaternion.
1489
+
1490
+ Returns
1491
+ =======
1492
+
1493
+ True : if the axis of the pure quaternions seen as 3D vectors
1494
+ q1, q2, and q3 are coplanar.
1495
+ False : if the axis of the pure quaternions seen as 3D vectors
1496
+ q1, q2, and q3 are not coplanar.
1497
+ None : if the axis of the pure quaternions seen as 3D vectors
1498
+ q1, q2, and q3 are coplanar is unknown.
1499
+
1500
+ Examples
1501
+ ========
1502
+
1503
+ >>> from sympy.algebras.quaternion import Quaternion
1504
+ >>> q1 = Quaternion(0, 4, 4, 4)
1505
+ >>> q2 = Quaternion(0, 8, 8, 8)
1506
+ >>> q3 = Quaternion(0, 24, 24, 24)
1507
+ >>> Quaternion.vector_coplanar(q1, q2, q3)
1508
+ True
1509
+
1510
+ >>> q1 = Quaternion(0, 8, 16, 8)
1511
+ >>> q2 = Quaternion(0, 8, 3, 12)
1512
+ >>> Quaternion.vector_coplanar(q1, q2, q3)
1513
+ False
1514
+
1515
+ See Also
1516
+ ========
1517
+
1518
+ axis
1519
+ is_pure
1520
+
1521
+ """
1522
+
1523
+ if fuzzy_not(q1.is_pure()) or fuzzy_not(q2.is_pure()) or fuzzy_not(q3.is_pure()):
1524
+ raise ValueError('The given quaternions must be pure')
1525
+
1526
+ M = Matrix([[q1.b, q1.c, q1.d], [q2.b, q2.c, q2.d], [q3.b, q3.c, q3.d]]).det()
1527
+ return M.is_zero
1528
+
1529
+ def parallel(self, other):
1530
+ """
1531
+ Returns True if the two pure quaternions seen as 3D vectors are parallel.
1532
+
1533
+ Explanation
1534
+ ===========
1535
+
1536
+ Two pure quaternions are called parallel when their vector product is commutative which
1537
+ implies that the quaternions seen as 3D vectors have same direction.
1538
+
1539
+ Parameters
1540
+ ==========
1541
+
1542
+ other : a Quaternion
1543
+
1544
+ Returns
1545
+ =======
1546
+
1547
+ True : if the two pure quaternions seen as 3D vectors are parallel.
1548
+ False : if the two pure quaternions seen as 3D vectors are not parallel.
1549
+ None : if the two pure quaternions seen as 3D vectors are parallel is unknown.
1550
+
1551
+ Examples
1552
+ ========
1553
+
1554
+ >>> from sympy.algebras.quaternion import Quaternion
1555
+ >>> q = Quaternion(0, 4, 4, 4)
1556
+ >>> q1 = Quaternion(0, 8, 8, 8)
1557
+ >>> q.parallel(q1)
1558
+ True
1559
+
1560
+ >>> q1 = Quaternion(0, 8, 13, 12)
1561
+ >>> q.parallel(q1)
1562
+ False
1563
+
1564
+ """
1565
+
1566
+ if fuzzy_not(self.is_pure()) or fuzzy_not(other.is_pure()):
1567
+ raise ValueError('The provided quaternions must be pure')
1568
+
1569
+ return (self*other - other*self).is_zero_quaternion()
1570
+
1571
+ def orthogonal(self, other):
1572
+ """
1573
+ Returns the orthogonality of two quaternions.
1574
+
1575
+ Explanation
1576
+ ===========
1577
+
1578
+ Two pure quaternions are called orthogonal when their product is anti-commutative.
1579
+
1580
+ Parameters
1581
+ ==========
1582
+
1583
+ other : a Quaternion
1584
+
1585
+ Returns
1586
+ =======
1587
+
1588
+ True : if the two pure quaternions seen as 3D vectors are orthogonal.
1589
+ False : if the two pure quaternions seen as 3D vectors are not orthogonal.
1590
+ None : if the two pure quaternions seen as 3D vectors are orthogonal is unknown.
1591
+
1592
+ Examples
1593
+ ========
1594
+
1595
+ >>> from sympy.algebras.quaternion import Quaternion
1596
+ >>> q = Quaternion(0, 4, 4, 4)
1597
+ >>> q1 = Quaternion(0, 8, 8, 8)
1598
+ >>> q.orthogonal(q1)
1599
+ False
1600
+
1601
+ >>> q1 = Quaternion(0, 2, 2, 0)
1602
+ >>> q = Quaternion(0, 2, -2, 0)
1603
+ >>> q.orthogonal(q1)
1604
+ True
1605
+
1606
+ """
1607
+
1608
+ if fuzzy_not(self.is_pure()) or fuzzy_not(other.is_pure()):
1609
+ raise ValueError('The given quaternions must be pure')
1610
+
1611
+ return (self*other + other*self).is_zero_quaternion()
1612
+
1613
+ def index_vector(self):
1614
+ r"""
1615
+ Returns the index vector of the quaternion.
1616
+
1617
+ Explanation
1618
+ ===========
1619
+
1620
+ The index vector is given by $\mathbf{T}(q)$, the norm (or magnitude) of
1621
+ the quaternion $q$, multiplied by $\mathbf{Ax}(q)$, the axis of $q$.
1622
+
1623
+ Returns
1624
+ =======
1625
+
1626
+ Quaternion: representing index vector of the provided quaternion.
1627
+
1628
+ Examples
1629
+ ========
1630
+
1631
+ >>> from sympy.algebras.quaternion import Quaternion
1632
+ >>> q = Quaternion(2, 4, 2, 4)
1633
+ >>> q.index_vector()
1634
+ 0 + 4*sqrt(10)/3*i + 2*sqrt(10)/3*j + 4*sqrt(10)/3*k
1635
+
1636
+ See Also
1637
+ ========
1638
+
1639
+ axis
1640
+ norm
1641
+
1642
+ """
1643
+
1644
+ return self.norm() * self.axis()
1645
+
1646
+ def mensor(self):
1647
+ """
1648
+ Returns the natural logarithm of the norm(magnitude) of the quaternion.
1649
+
1650
+ Examples
1651
+ ========
1652
+
1653
+ >>> from sympy.algebras.quaternion import Quaternion
1654
+ >>> q = Quaternion(2, 4, 2, 4)
1655
+ >>> q.mensor()
1656
+ log(2*sqrt(10))
1657
+ >>> q.norm()
1658
+ 2*sqrt(10)
1659
+
1660
+ See Also
1661
+ ========
1662
+
1663
+ norm
1664
+
1665
+ """
1666
+
1667
+ return ln(self.norm())
lib/python3.12/site-packages/sympy/algebras/tests/__init__.py ADDED
File without changes
lib/python3.12/site-packages/sympy/algebras/tests/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (199 Bytes). View file
 
lib/python3.12/site-packages/sympy/algebras/tests/__pycache__/test_quaternion.cpython-312.pyc ADDED
Binary file (35.2 kB). View file
 
lib/python3.12/site-packages/sympy/algebras/tests/test_quaternion.py ADDED
@@ -0,0 +1,428 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.testing.pytest import slow
2
+ from sympy.core.function import diff
3
+ from sympy.core.function import expand
4
+ from sympy.core.numbers import (E, I, Rational, pi)
5
+ from sympy.core.singleton import S
6
+ from sympy.core.symbol import (Symbol, symbols)
7
+ from sympy.functions.elementary.complexes import (Abs, conjugate, im, re, sign)
8
+ from sympy.functions.elementary.exponential import log
9
+ from sympy.functions.elementary.miscellaneous import sqrt
10
+ from sympy.functions.elementary.trigonometric import (acos, asin, cos, sin, atan2, atan)
11
+ from sympy.integrals.integrals import integrate
12
+ from sympy.matrices.dense import Matrix
13
+ from sympy.simplify import simplify
14
+ from sympy.simplify.trigsimp import trigsimp
15
+ from sympy.algebras.quaternion import Quaternion
16
+ from sympy.testing.pytest import raises
17
+ import math
18
+ from itertools import permutations, product
19
+
20
+ w, x, y, z = symbols('w:z')
21
+ phi = symbols('phi')
22
+
23
+ def test_quaternion_construction():
24
+ q = Quaternion(w, x, y, z)
25
+ assert q + q == Quaternion(2*w, 2*x, 2*y, 2*z)
26
+
27
+ q2 = Quaternion.from_axis_angle((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3),
28
+ pi*Rational(2, 3))
29
+ assert q2 == Quaternion(S.Half, S.Half,
30
+ S.Half, S.Half)
31
+
32
+ M = Matrix([[cos(phi), -sin(phi), 0], [sin(phi), cos(phi), 0], [0, 0, 1]])
33
+ q3 = trigsimp(Quaternion.from_rotation_matrix(M))
34
+ assert q3 == Quaternion(
35
+ sqrt(2)*sqrt(cos(phi) + 1)/2, 0, 0, sqrt(2 - 2*cos(phi))*sign(sin(phi))/2)
36
+
37
+ nc = Symbol('nc', commutative=False)
38
+ raises(ValueError, lambda: Quaternion(w, x, nc, z))
39
+
40
+
41
+ def test_quaternion_construction_norm():
42
+ q1 = Quaternion(*symbols('a:d'))
43
+
44
+ q2 = Quaternion(w, x, y, z)
45
+ assert expand((q1*q2).norm()**2 - (q1.norm()**2 * q2.norm()**2)) == 0
46
+
47
+ q3 = Quaternion(w, x, y, z, norm=1)
48
+ assert (q1 * q3).norm() == q1.norm()
49
+
50
+
51
+ def test_issue_25254():
52
+ # calculating the inverse cached the norm which caused problems
53
+ # when multiplying
54
+ p = Quaternion(1, 0, 0, 0)
55
+ q = Quaternion.from_axis_angle((1, 1, 1), 3 * math.pi/4)
56
+ qi = q.inverse() # this operation cached the norm
57
+ test = q * p * qi
58
+ assert ((test - p).norm() < 1E-10)
59
+
60
+
61
+ def test_to_and_from_Matrix():
62
+ q = Quaternion(w, x, y, z)
63
+ q_full = Quaternion.from_Matrix(q.to_Matrix())
64
+ q_vect = Quaternion.from_Matrix(q.to_Matrix(True))
65
+ assert (q - q_full).is_zero_quaternion()
66
+ assert (q.vector_part() - q_vect).is_zero_quaternion()
67
+
68
+
69
+ def test_product_matrices():
70
+ q1 = Quaternion(w, x, y, z)
71
+ q2 = Quaternion(*(symbols("a:d")))
72
+ assert (q1 * q2).to_Matrix() == q1.product_matrix_left * q2.to_Matrix()
73
+ assert (q1 * q2).to_Matrix() == q2.product_matrix_right * q1.to_Matrix()
74
+
75
+ R1 = (q1.product_matrix_left * q1.product_matrix_right.T)[1:, 1:]
76
+ R2 = simplify(q1.to_rotation_matrix()*q1.norm()**2)
77
+ assert R1 == R2
78
+
79
+
80
+ def test_quaternion_axis_angle():
81
+
82
+ test_data = [ # axis, angle, expected_quaternion
83
+ ((1, 0, 0), 0, (1, 0, 0, 0)),
84
+ ((1, 0, 0), pi/2, (sqrt(2)/2, sqrt(2)/2, 0, 0)),
85
+ ((0, 1, 0), pi/2, (sqrt(2)/2, 0, sqrt(2)/2, 0)),
86
+ ((0, 0, 1), pi/2, (sqrt(2)/2, 0, 0, sqrt(2)/2)),
87
+ ((1, 0, 0), pi, (0, 1, 0, 0)),
88
+ ((0, 1, 0), pi, (0, 0, 1, 0)),
89
+ ((0, 0, 1), pi, (0, 0, 0, 1)),
90
+ ((1, 1, 1), pi, (0, 1/sqrt(3),1/sqrt(3),1/sqrt(3))),
91
+ ((sqrt(3)/3, sqrt(3)/3, sqrt(3)/3), pi*2/3, (S.Half, S.Half, S.Half, S.Half))
92
+ ]
93
+
94
+ for axis, angle, expected in test_data:
95
+ assert Quaternion.from_axis_angle(axis, angle) == Quaternion(*expected)
96
+
97
+
98
+ def test_quaternion_axis_angle_simplification():
99
+ result = Quaternion.from_axis_angle((1, 2, 3), asin(4))
100
+ assert result.a == cos(asin(4)/2)
101
+ assert result.b == sqrt(14)*sin(asin(4)/2)/14
102
+ assert result.c == sqrt(14)*sin(asin(4)/2)/7
103
+ assert result.d == 3*sqrt(14)*sin(asin(4)/2)/14
104
+
105
+ def test_quaternion_complex_real_addition():
106
+ a = symbols("a", complex=True)
107
+ b = symbols("b", real=True)
108
+ # This symbol is not complex:
109
+ c = symbols("c", commutative=False)
110
+
111
+ q = Quaternion(w, x, y, z)
112
+ assert a + q == Quaternion(w + re(a), x + im(a), y, z)
113
+ assert 1 + q == Quaternion(1 + w, x, y, z)
114
+ assert I + q == Quaternion(w, 1 + x, y, z)
115
+ assert b + q == Quaternion(w + b, x, y, z)
116
+ raises(ValueError, lambda: c + q)
117
+ raises(ValueError, lambda: q * c)
118
+ raises(ValueError, lambda: c * q)
119
+
120
+ assert -q == Quaternion(-w, -x, -y, -z)
121
+
122
+ q1 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
123
+ q2 = Quaternion(1, 4, 7, 8)
124
+
125
+ assert q1 + (2 + 3*I) == Quaternion(5 + 7*I, 2 + 5*I, 0, 7 + 8*I)
126
+ assert q2 + (2 + 3*I) == Quaternion(3, 7, 7, 8)
127
+ assert q1 * (2 + 3*I) == \
128
+ Quaternion((2 + 3*I)*(3 + 4*I), (2 + 3*I)*(2 + 5*I), 0, (2 + 3*I)*(7 + 8*I))
129
+ assert q2 * (2 + 3*I) == Quaternion(-10, 11, 38, -5)
130
+
131
+ q1 = Quaternion(1, 2, 3, 4)
132
+ q0 = Quaternion(0, 0, 0, 0)
133
+ assert q1 + q0 == q1
134
+ assert q1 - q0 == q1
135
+ assert q1 - q1 == q0
136
+
137
+
138
+ def test_quaternion_subs():
139
+ q = Quaternion.from_axis_angle((0, 0, 1), phi)
140
+ assert q.subs(phi, 0) == Quaternion(1, 0, 0, 0)
141
+
142
+
143
+ def test_quaternion_evalf():
144
+ assert (Quaternion(sqrt(2), 0, 0, sqrt(3)).evalf() ==
145
+ Quaternion(sqrt(2).evalf(), 0, 0, sqrt(3).evalf()))
146
+ assert (Quaternion(1/sqrt(2), 0, 0, 1/sqrt(2)).evalf() ==
147
+ Quaternion((1/sqrt(2)).evalf(), 0, 0, (1/sqrt(2)).evalf()))
148
+
149
+
150
+ def test_quaternion_functions():
151
+ q = Quaternion(w, x, y, z)
152
+ q1 = Quaternion(1, 2, 3, 4)
153
+ q0 = Quaternion(0, 0, 0, 0)
154
+
155
+ assert conjugate(q) == Quaternion(w, -x, -y, -z)
156
+ assert q.norm() == sqrt(w**2 + x**2 + y**2 + z**2)
157
+ assert q.normalize() == Quaternion(w, x, y, z) / sqrt(w**2 + x**2 + y**2 + z**2)
158
+ assert q.inverse() == Quaternion(w, -x, -y, -z) / (w**2 + x**2 + y**2 + z**2)
159
+ assert q.inverse() == q.pow(-1)
160
+ raises(ValueError, lambda: q0.inverse())
161
+ assert q.pow(2) == Quaternion(w**2 - x**2 - y**2 - z**2, 2*w*x, 2*w*y, 2*w*z)
162
+ assert q**(2) == Quaternion(w**2 - x**2 - y**2 - z**2, 2*w*x, 2*w*y, 2*w*z)
163
+ assert q1.pow(-2) == Quaternion(
164
+ Rational(-7, 225), Rational(-1, 225), Rational(-1, 150), Rational(-2, 225))
165
+ assert q1**(-2) == Quaternion(
166
+ Rational(-7, 225), Rational(-1, 225), Rational(-1, 150), Rational(-2, 225))
167
+ assert q1.pow(-0.5) == NotImplemented
168
+ raises(TypeError, lambda: q1**(-0.5))
169
+
170
+ assert q1.exp() == \
171
+ Quaternion(E * cos(sqrt(29)),
172
+ 2 * sqrt(29) * E * sin(sqrt(29)) / 29,
173
+ 3 * sqrt(29) * E * sin(sqrt(29)) / 29,
174
+ 4 * sqrt(29) * E * sin(sqrt(29)) / 29)
175
+ assert q1.log() == \
176
+ Quaternion(log(sqrt(30)),
177
+ 2 * sqrt(29) * acos(sqrt(30)/30) / 29,
178
+ 3 * sqrt(29) * acos(sqrt(30)/30) / 29,
179
+ 4 * sqrt(29) * acos(sqrt(30)/30) / 29)
180
+
181
+ assert q1.pow_cos_sin(2) == \
182
+ Quaternion(30 * cos(2 * acos(sqrt(30)/30)),
183
+ 60 * sqrt(29) * sin(2 * acos(sqrt(30)/30)) / 29,
184
+ 90 * sqrt(29) * sin(2 * acos(sqrt(30)/30)) / 29,
185
+ 120 * sqrt(29) * sin(2 * acos(sqrt(30)/30)) / 29)
186
+
187
+ assert diff(Quaternion(x, x, x, x), x) == Quaternion(1, 1, 1, 1)
188
+
189
+ assert integrate(Quaternion(x, x, x, x), x) == \
190
+ Quaternion(x**2 / 2, x**2 / 2, x**2 / 2, x**2 / 2)
191
+
192
+ assert Quaternion.rotate_point((1, 1, 1), q1) == (S.One / 5, 1, S(7) / 5)
193
+ n = Symbol('n')
194
+ raises(TypeError, lambda: q1**n)
195
+ n = Symbol('n', integer=True)
196
+ raises(TypeError, lambda: q1**n)
197
+
198
+ assert Quaternion(22, 23, 55, 8).scalar_part() == 22
199
+ assert Quaternion(w, x, y, z).scalar_part() == w
200
+
201
+ assert Quaternion(22, 23, 55, 8).vector_part() == Quaternion(0, 23, 55, 8)
202
+ assert Quaternion(w, x, y, z).vector_part() == Quaternion(0, x, y, z)
203
+
204
+ assert q1.axis() == Quaternion(0, 2*sqrt(29)/29, 3*sqrt(29)/29, 4*sqrt(29)/29)
205
+ assert q1.axis().pow(2) == Quaternion(-1, 0, 0, 0)
206
+ assert q0.axis().scalar_part() == 0
207
+ assert (q.axis() == Quaternion(0,
208
+ x/sqrt(x**2 + y**2 + z**2),
209
+ y/sqrt(x**2 + y**2 + z**2),
210
+ z/sqrt(x**2 + y**2 + z**2)))
211
+
212
+ assert q0.is_pure() is True
213
+ assert q1.is_pure() is False
214
+ assert Quaternion(0, 0, 0, 3).is_pure() is True
215
+ assert Quaternion(0, 2, 10, 3).is_pure() is True
216
+ assert Quaternion(w, 2, 10, 3).is_pure() is None
217
+
218
+ assert q1.angle() == 2*atan(sqrt(29))
219
+ assert q.angle() == 2*atan2(sqrt(x**2 + y**2 + z**2), w)
220
+
221
+ assert Quaternion.arc_coplanar(q1, Quaternion(2, 4, 6, 8)) is True
222
+ assert Quaternion.arc_coplanar(q1, Quaternion(1, -2, -3, -4)) is True
223
+ assert Quaternion.arc_coplanar(q1, Quaternion(1, 8, 12, 16)) is True
224
+ assert Quaternion.arc_coplanar(q1, Quaternion(1, 2, 3, 4)) is True
225
+ assert Quaternion.arc_coplanar(q1, Quaternion(w, 4, 6, 8)) is True
226
+ assert Quaternion.arc_coplanar(q1, Quaternion(2, 7, 4, 1)) is False
227
+ assert Quaternion.arc_coplanar(q1, Quaternion(w, x, y, z)) is None
228
+ raises(ValueError, lambda: Quaternion.arc_coplanar(q1, q0))
229
+
230
+ assert Quaternion.vector_coplanar(
231
+ Quaternion(0, 8, 12, 16),
232
+ Quaternion(0, 4, 6, 8),
233
+ Quaternion(0, 2, 3, 4)) is True
234
+ assert Quaternion.vector_coplanar(
235
+ Quaternion(0, 0, 0, 0), Quaternion(0, 4, 6, 8), Quaternion(0, 2, 3, 4)) is True
236
+ assert Quaternion.vector_coplanar(
237
+ Quaternion(0, 8, 2, 6), Quaternion(0, 1, 6, 6), Quaternion(0, 0, 3, 4)) is False
238
+ assert Quaternion.vector_coplanar(
239
+ Quaternion(0, 1, 3, 4),
240
+ Quaternion(0, 4, w, 6),
241
+ Quaternion(0, 6, 8, 1)) is None
242
+ raises(ValueError, lambda:
243
+ Quaternion.vector_coplanar(q0, Quaternion(0, 4, 6, 8), q1))
244
+
245
+ assert Quaternion(0, 1, 2, 3).parallel(Quaternion(0, 2, 4, 6)) is True
246
+ assert Quaternion(0, 1, 2, 3).parallel(Quaternion(0, 2, 2, 6)) is False
247
+ assert Quaternion(0, 1, 2, 3).parallel(Quaternion(w, x, y, 6)) is None
248
+ raises(ValueError, lambda: q0.parallel(q1))
249
+
250
+ assert Quaternion(0, 1, 2, 3).orthogonal(Quaternion(0, -2, 1, 0)) is True
251
+ assert Quaternion(0, 2, 4, 7).orthogonal(Quaternion(0, 2, 2, 6)) is False
252
+ assert Quaternion(0, 2, 4, 7).orthogonal(Quaternion(w, x, y, 6)) is None
253
+ raises(ValueError, lambda: q0.orthogonal(q1))
254
+
255
+ assert q1.index_vector() == Quaternion(
256
+ 0, 2*sqrt(870)/29,
257
+ 3*sqrt(870)/29,
258
+ 4*sqrt(870)/29)
259
+ assert Quaternion(0, 3, 9, 4).index_vector() == Quaternion(0, 3, 9, 4)
260
+
261
+ assert Quaternion(4, 3, 9, 4).mensor() == log(sqrt(122))
262
+ assert Quaternion(3, 3, 0, 2).mensor() == log(sqrt(22))
263
+
264
+ assert q0.is_zero_quaternion() is True
265
+ assert q1.is_zero_quaternion() is False
266
+ assert Quaternion(w, 0, 0, 0).is_zero_quaternion() is None
267
+
268
+ def test_quaternion_conversions():
269
+ q1 = Quaternion(1, 2, 3, 4)
270
+
271
+ assert q1.to_axis_angle() == ((2 * sqrt(29)/29,
272
+ 3 * sqrt(29)/29,
273
+ 4 * sqrt(29)/29),
274
+ 2 * acos(sqrt(30)/30))
275
+
276
+ assert (q1.to_rotation_matrix() ==
277
+ Matrix([[Rational(-2, 3), Rational(2, 15), Rational(11, 15)],
278
+ [Rational(2, 3), Rational(-1, 3), Rational(2, 3)],
279
+ [Rational(1, 3), Rational(14, 15), Rational(2, 15)]]))
280
+
281
+ assert (q1.to_rotation_matrix((1, 1, 1)) ==
282
+ Matrix([
283
+ [Rational(-2, 3), Rational(2, 15), Rational(11, 15), Rational(4, 5)],
284
+ [Rational(2, 3), Rational(-1, 3), Rational(2, 3), S.Zero],
285
+ [Rational(1, 3), Rational(14, 15), Rational(2, 15), Rational(-2, 5)],
286
+ [S.Zero, S.Zero, S.Zero, S.One]]))
287
+
288
+ theta = symbols("theta", real=True)
289
+ q2 = Quaternion(cos(theta/2), 0, 0, sin(theta/2))
290
+
291
+ assert trigsimp(q2.to_rotation_matrix()) == Matrix([
292
+ [cos(theta), -sin(theta), 0],
293
+ [sin(theta), cos(theta), 0],
294
+ [0, 0, 1]])
295
+
296
+ assert q2.to_axis_angle() == ((0, 0, sin(theta/2)/Abs(sin(theta/2))),
297
+ 2*acos(cos(theta/2)))
298
+
299
+ assert trigsimp(q2.to_rotation_matrix((1, 1, 1))) == Matrix([
300
+ [cos(theta), -sin(theta), 0, sin(theta) - cos(theta) + 1],
301
+ [sin(theta), cos(theta), 0, -sin(theta) - cos(theta) + 1],
302
+ [0, 0, 1, 0],
303
+ [0, 0, 0, 1]])
304
+
305
+
306
+ def test_rotation_matrix_homogeneous():
307
+ q = Quaternion(w, x, y, z)
308
+ R1 = q.to_rotation_matrix(homogeneous=True) * q.norm()**2
309
+ R2 = simplify(q.to_rotation_matrix(homogeneous=False) * q.norm()**2)
310
+ assert R1 == R2
311
+
312
+
313
+ def test_quaternion_rotation_iss1593():
314
+ """
315
+ There was a sign mistake in the definition,
316
+ of the rotation matrix. This tests that particular sign mistake.
317
+ See issue 1593 for reference.
318
+ See wikipedia
319
+ https://en.wikipedia.org/wiki/Quaternions_and_spatial_rotation#Quaternion-derived_rotation_matrix
320
+ for the correct definition
321
+ """
322
+ q = Quaternion(cos(phi/2), sin(phi/2), 0, 0)
323
+ assert(trigsimp(q.to_rotation_matrix()) == Matrix([
324
+ [1, 0, 0],
325
+ [0, cos(phi), -sin(phi)],
326
+ [0, sin(phi), cos(phi)]]))
327
+
328
+
329
+ def test_quaternion_multiplication():
330
+ q1 = Quaternion(3 + 4*I, 2 + 5*I, 0, 7 + 8*I, real_field = False)
331
+ q2 = Quaternion(1, 2, 3, 5)
332
+ q3 = Quaternion(1, 1, 1, y)
333
+
334
+ assert Quaternion._generic_mul(S(4), S.One) == 4
335
+ assert (Quaternion._generic_mul(S(4), q1) ==
336
+ Quaternion(12 + 16*I, 8 + 20*I, 0, 28 + 32*I))
337
+ assert q2.mul(2) == Quaternion(2, 4, 6, 10)
338
+ assert q2.mul(q3) == Quaternion(-5*y - 4, 3*y - 2, 9 - 2*y, y + 4)
339
+ assert q2.mul(q3) == q2*q3
340
+
341
+ z = symbols('z', complex=True)
342
+ z_quat = Quaternion(re(z), im(z), 0, 0)
343
+ q = Quaternion(*symbols('q:4', real=True))
344
+
345
+ assert z * q == z_quat * q
346
+ assert q * z == q * z_quat
347
+
348
+
349
+ def test_issue_16318():
350
+ #for rtruediv
351
+ q0 = Quaternion(0, 0, 0, 0)
352
+ raises(ValueError, lambda: 1/q0)
353
+ #for rotate_point
354
+ q = Quaternion(1, 2, 3, 4)
355
+ (axis, angle) = q.to_axis_angle()
356
+ assert Quaternion.rotate_point((1, 1, 1), (axis, angle)) == (S.One / 5, 1, S(7) / 5)
357
+ #test for to_axis_angle
358
+ q = Quaternion(-1, 1, 1, 1)
359
+ axis = (-sqrt(3)/3, -sqrt(3)/3, -sqrt(3)/3)
360
+ angle = 2*pi/3
361
+ assert (axis, angle) == q.to_axis_angle()
362
+
363
+
364
+ @slow
365
+ def test_to_euler():
366
+ q = Quaternion(w, x, y, z)
367
+ q_normalized = q.normalize()
368
+
369
+ seqs = ['zxy', 'zyx', 'zyz', 'zxz']
370
+ seqs += [seq.upper() for seq in seqs]
371
+
372
+ for seq in seqs:
373
+ euler_from_q = q.to_euler(seq)
374
+ q_back = simplify(Quaternion.from_euler(euler_from_q, seq))
375
+ assert q_back == q_normalized
376
+
377
+
378
+ def test_to_euler_iss24504():
379
+ """
380
+ There was a mistake in the degenerate case testing
381
+ See issue 24504 for reference.
382
+ """
383
+ q = Quaternion.from_euler((phi, 0, 0), 'zyz')
384
+ assert trigsimp(q.to_euler('zyz'), inverse=True) == (phi, 0, 0)
385
+
386
+
387
+ def test_to_euler_numerical_singilarities():
388
+
389
+ def test_one_case(angles, seq):
390
+ q = Quaternion.from_euler(angles, seq)
391
+ assert q.to_euler(seq) == angles
392
+
393
+ # symmetric
394
+ test_one_case((pi/2, 0, 0), 'zyz')
395
+ test_one_case((pi/2, 0, 0), 'ZYZ')
396
+ test_one_case((pi/2, pi, 0), 'zyz')
397
+ test_one_case((pi/2, pi, 0), 'ZYZ')
398
+
399
+ # asymmetric
400
+ test_one_case((pi/2, pi/2, 0), 'zyx')
401
+ test_one_case((pi/2, -pi/2, 0), 'zyx')
402
+ test_one_case((pi/2, pi/2, 0), 'ZYX')
403
+ test_one_case((pi/2, -pi/2, 0), 'ZYX')
404
+
405
+
406
+ @slow
407
+ def test_to_euler_options():
408
+ def test_one_case(q):
409
+ angles1 = Matrix(q.to_euler(seq, True, True))
410
+ angles2 = Matrix(q.to_euler(seq, False, False))
411
+ angle_errors = simplify(angles1-angles2).evalf()
412
+ for angle_error in angle_errors:
413
+ # forcing angles to set {-pi, pi}
414
+ angle_error = (angle_error + pi) % (2 * pi) - pi
415
+ assert angle_error < 10e-7
416
+
417
+ for xyz in ('xyz', 'XYZ'):
418
+ for seq_tuple in permutations(xyz):
419
+ for symmetric in (True, False):
420
+ if symmetric:
421
+ seq = ''.join([seq_tuple[0], seq_tuple[1], seq_tuple[0]])
422
+ else:
423
+ seq = ''.join(seq_tuple)
424
+
425
+ for elements in product([-1, 0, 1], repeat=4):
426
+ q = Quaternion(*elements)
427
+ if not q.is_zero_quaternion():
428
+ test_one_case(q)
lib/python3.12/site-packages/sympy/calculus/__init__.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Calculus-related methods."""
2
+
3
+ from .euler import euler_equations
4
+ from .singularities import (singularities, is_increasing,
5
+ is_strictly_increasing, is_decreasing,
6
+ is_strictly_decreasing, is_monotonic)
7
+ from .finite_diff import finite_diff_weights, apply_finite_diff, differentiate_finite
8
+ from .util import (periodicity, not_empty_in, is_convex,
9
+ stationary_points, minimum, maximum)
10
+ from .accumulationbounds import AccumBounds
11
+
12
+ __all__ = [
13
+ 'euler_equations',
14
+
15
+ 'singularities', 'is_increasing',
16
+ 'is_strictly_increasing', 'is_decreasing',
17
+ 'is_strictly_decreasing', 'is_monotonic',
18
+
19
+ 'finite_diff_weights', 'apply_finite_diff', 'differentiate_finite',
20
+
21
+ 'periodicity', 'not_empty_in', 'is_convex', 'stationary_points',
22
+ 'minimum', 'maximum',
23
+
24
+ 'AccumBounds'
25
+ ]
lib/python3.12/site-packages/sympy/calculus/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (909 Bytes). View file
 
lib/python3.12/site-packages/sympy/calculus/__pycache__/accumulationbounds.cpython-312.pyc ADDED
Binary file (35.4 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/__pycache__/euler.cpython-312.pyc ADDED
Binary file (4.77 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/__pycache__/finite_diff.cpython-312.pyc ADDED
Binary file (19.6 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/__pycache__/singularities.cpython-312.pyc ADDED
Binary file (14.1 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/__pycache__/util.cpython-312.pyc ADDED
Binary file (33.3 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/accumulationbounds.py ADDED
@@ -0,0 +1,804 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core import Add, Mul, Pow, S
2
+ from sympy.core.basic import Basic
3
+ from sympy.core.expr import Expr
4
+ from sympy.core.numbers import _sympifyit, oo, zoo
5
+ from sympy.core.relational import is_le, is_lt, is_ge, is_gt
6
+ from sympy.core.sympify import _sympify
7
+ from sympy.functions.elementary.miscellaneous import Min, Max
8
+ from sympy.logic.boolalg import And
9
+ from sympy.multipledispatch import dispatch
10
+ from sympy.series.order import Order
11
+ from sympy.sets.sets import FiniteSet
12
+
13
+
14
+ class AccumulationBounds(Expr):
15
+ r"""An accumulation bounds.
16
+
17
+ # Note AccumulationBounds has an alias: AccumBounds
18
+
19
+ AccumulationBounds represent an interval `[a, b]`, which is always closed
20
+ at the ends. Here `a` and `b` can be any value from extended real numbers.
21
+
22
+ The intended meaning of AccummulationBounds is to give an approximate
23
+ location of the accumulation points of a real function at a limit point.
24
+
25
+ Let `a` and `b` be reals such that `a \le b`.
26
+
27
+ `\left\langle a, b\right\rangle = \{x \in \mathbb{R} \mid a \le x \le b\}`
28
+
29
+ `\left\langle -\infty, b\right\rangle = \{x \in \mathbb{R} \mid x \le b\} \cup \{-\infty, \infty\}`
30
+
31
+ `\left\langle a, \infty \right\rangle = \{x \in \mathbb{R} \mid a \le x\} \cup \{-\infty, \infty\}`
32
+
33
+ `\left\langle -\infty, \infty \right\rangle = \mathbb{R} \cup \{-\infty, \infty\}`
34
+
35
+ ``oo`` and ``-oo`` are added to the second and third definition respectively,
36
+ since if either ``-oo`` or ``oo`` is an argument, then the other one should
37
+ be included (though not as an end point). This is forced, since we have,
38
+ for example, ``1/AccumBounds(0, 1) = AccumBounds(1, oo)``, and the limit at
39
+ `0` is not one-sided. As `x` tends to `0-`, then `1/x \rightarrow -\infty`, so `-\infty`
40
+ should be interpreted as belonging to ``AccumBounds(1, oo)`` though it need
41
+ not appear explicitly.
42
+
43
+ In many cases it suffices to know that the limit set is bounded.
44
+ However, in some other cases more exact information could be useful.
45
+ For example, all accumulation values of `\cos(x) + 1` are non-negative.
46
+ (``AccumBounds(-1, 1) + 1 = AccumBounds(0, 2)``)
47
+
48
+ A AccumulationBounds object is defined to be real AccumulationBounds,
49
+ if its end points are finite reals.
50
+
51
+ Let `X`, `Y` be real AccumulationBounds, then their sum, difference,
52
+ product are defined to be the following sets:
53
+
54
+ `X + Y = \{ x+y \mid x \in X \cap y \in Y\}`
55
+
56
+ `X - Y = \{ x-y \mid x \in X \cap y \in Y\}`
57
+
58
+ `X \times Y = \{ x \times y \mid x \in X \cap y \in Y\}`
59
+
60
+ When an AccumBounds is raised to a negative power, if 0 is contained
61
+ between the bounds then an infinite range is returned, otherwise if an
62
+ endpoint is 0 then a semi-infinite range with consistent sign will be returned.
63
+
64
+ AccumBounds in expressions behave a lot like Intervals but the
65
+ semantics are not necessarily the same. Division (or exponentiation
66
+ to a negative integer power) could be handled with *intervals* by
67
+ returning a union of the results obtained after splitting the
68
+ bounds between negatives and positives, but that is not done with
69
+ AccumBounds. In addition, bounds are assumed to be independent of
70
+ each other; if the same bound is used in more than one place in an
71
+ expression, the result may not be the supremum or infimum of the
72
+ expression (see below). Finally, when a boundary is ``1``,
73
+ exponentiation to the power of ``oo`` yields ``oo``, neither
74
+ ``1`` nor ``nan``.
75
+
76
+ Examples
77
+ ========
78
+
79
+ >>> from sympy import AccumBounds, sin, exp, log, pi, E, S, oo
80
+ >>> from sympy.abc import x
81
+
82
+ >>> AccumBounds(0, 1) + AccumBounds(1, 2)
83
+ AccumBounds(1, 3)
84
+
85
+ >>> AccumBounds(0, 1) - AccumBounds(0, 2)
86
+ AccumBounds(-2, 1)
87
+
88
+ >>> AccumBounds(-2, 3)*AccumBounds(-1, 1)
89
+ AccumBounds(-3, 3)
90
+
91
+ >>> AccumBounds(1, 2)*AccumBounds(3, 5)
92
+ AccumBounds(3, 10)
93
+
94
+ The exponentiation of AccumulationBounds is defined
95
+ as follows:
96
+
97
+ If 0 does not belong to `X` or `n > 0` then
98
+
99
+ `X^n = \{ x^n \mid x \in X\}`
100
+
101
+ >>> AccumBounds(1, 4)**(S(1)/2)
102
+ AccumBounds(1, 2)
103
+
104
+ otherwise, an infinite or semi-infinite result is obtained:
105
+
106
+ >>> 1/AccumBounds(-1, 1)
107
+ AccumBounds(-oo, oo)
108
+ >>> 1/AccumBounds(0, 2)
109
+ AccumBounds(1/2, oo)
110
+ >>> 1/AccumBounds(-oo, 0)
111
+ AccumBounds(-oo, 0)
112
+
113
+ A boundary of 1 will always generate all nonnegatives:
114
+
115
+ >>> AccumBounds(1, 2)**oo
116
+ AccumBounds(0, oo)
117
+ >>> AccumBounds(0, 1)**oo
118
+ AccumBounds(0, oo)
119
+
120
+ If the exponent is itself an AccumulationBounds or is not an
121
+ integer then unevaluated results will be returned unless the base
122
+ values are positive:
123
+
124
+ >>> AccumBounds(2, 3)**AccumBounds(-1, 2)
125
+ AccumBounds(1/3, 9)
126
+ >>> AccumBounds(-2, 3)**AccumBounds(-1, 2)
127
+ AccumBounds(-2, 3)**AccumBounds(-1, 2)
128
+
129
+ >>> AccumBounds(-2, -1)**(S(1)/2)
130
+ sqrt(AccumBounds(-2, -1))
131
+
132
+ Note: `\left\langle a, b\right\rangle^2` is not same as `\left\langle a, b\right\rangle \times \left\langle a, b\right\rangle`
133
+
134
+ >>> AccumBounds(-1, 1)**2
135
+ AccumBounds(0, 1)
136
+
137
+ >>> AccumBounds(1, 3) < 4
138
+ True
139
+
140
+ >>> AccumBounds(1, 3) < -1
141
+ False
142
+
143
+ Some elementary functions can also take AccumulationBounds as input.
144
+ A function `f` evaluated for some real AccumulationBounds `\left\langle a, b \right\rangle`
145
+ is defined as `f(\left\langle a, b\right\rangle) = \{ f(x) \mid a \le x \le b \}`
146
+
147
+ >>> sin(AccumBounds(pi/6, pi/3))
148
+ AccumBounds(1/2, sqrt(3)/2)
149
+
150
+ >>> exp(AccumBounds(0, 1))
151
+ AccumBounds(1, E)
152
+
153
+ >>> log(AccumBounds(1, E))
154
+ AccumBounds(0, 1)
155
+
156
+ Some symbol in an expression can be substituted for a AccumulationBounds
157
+ object. But it does not necessarily evaluate the AccumulationBounds for
158
+ that expression.
159
+
160
+ The same expression can be evaluated to different values depending upon
161
+ the form it is used for substitution since each instance of an
162
+ AccumulationBounds is considered independent. For example:
163
+
164
+ >>> (x**2 + 2*x + 1).subs(x, AccumBounds(-1, 1))
165
+ AccumBounds(-1, 4)
166
+
167
+ >>> ((x + 1)**2).subs(x, AccumBounds(-1, 1))
168
+ AccumBounds(0, 4)
169
+
170
+ References
171
+ ==========
172
+
173
+ .. [1] https://en.wikipedia.org/wiki/Interval_arithmetic
174
+
175
+ .. [2] https://fab.cba.mit.edu/classes/S62.12/docs/Hickey_interval.pdf
176
+
177
+ Notes
178
+ =====
179
+
180
+ Do not use ``AccumulationBounds`` for floating point interval arithmetic
181
+ calculations, use ``mpmath.iv`` instead.
182
+ """
183
+
184
+ is_extended_real = True
185
+ is_number = False
186
+
187
+ def __new__(cls, min, max):
188
+
189
+ min = _sympify(min)
190
+ max = _sympify(max)
191
+
192
+ # Only allow real intervals (use symbols with 'is_extended_real=True').
193
+ if not min.is_extended_real or not max.is_extended_real:
194
+ raise ValueError("Only real AccumulationBounds are supported")
195
+
196
+ if max == min:
197
+ return max
198
+
199
+ # Make sure that the created AccumBounds object will be valid.
200
+ if max.is_number and min.is_number:
201
+ bad = max.is_comparable and min.is_comparable and max < min
202
+ else:
203
+ bad = (max - min).is_extended_negative
204
+ if bad:
205
+ raise ValueError(
206
+ "Lower limit should be smaller than upper limit")
207
+
208
+ return Basic.__new__(cls, min, max)
209
+
210
+ # setting the operation priority
211
+ _op_priority = 11.0
212
+
213
+ def _eval_is_real(self):
214
+ if self.min.is_real and self.max.is_real:
215
+ return True
216
+
217
+ @property
218
+ def min(self):
219
+ """
220
+ Returns the minimum possible value attained by AccumulationBounds
221
+ object.
222
+
223
+ Examples
224
+ ========
225
+
226
+ >>> from sympy import AccumBounds
227
+ >>> AccumBounds(1, 3).min
228
+ 1
229
+
230
+ """
231
+ return self.args[0]
232
+
233
+ @property
234
+ def max(self):
235
+ """
236
+ Returns the maximum possible value attained by AccumulationBounds
237
+ object.
238
+
239
+ Examples
240
+ ========
241
+
242
+ >>> from sympy import AccumBounds
243
+ >>> AccumBounds(1, 3).max
244
+ 3
245
+
246
+ """
247
+ return self.args[1]
248
+
249
+ @property
250
+ def delta(self):
251
+ """
252
+ Returns the difference of maximum possible value attained by
253
+ AccumulationBounds object and minimum possible value attained
254
+ by AccumulationBounds object.
255
+
256
+ Examples
257
+ ========
258
+
259
+ >>> from sympy import AccumBounds
260
+ >>> AccumBounds(1, 3).delta
261
+ 2
262
+
263
+ """
264
+ return self.max - self.min
265
+
266
+ @property
267
+ def mid(self):
268
+ """
269
+ Returns the mean of maximum possible value attained by
270
+ AccumulationBounds object and minimum possible value
271
+ attained by AccumulationBounds object.
272
+
273
+ Examples
274
+ ========
275
+
276
+ >>> from sympy import AccumBounds
277
+ >>> AccumBounds(1, 3).mid
278
+ 2
279
+
280
+ """
281
+ return (self.min + self.max) / 2
282
+
283
+ @_sympifyit('other', NotImplemented)
284
+ def _eval_power(self, other):
285
+ return self.__pow__(other)
286
+
287
+ @_sympifyit('other', NotImplemented)
288
+ def __add__(self, other):
289
+ if isinstance(other, Expr):
290
+ if isinstance(other, AccumBounds):
291
+ return AccumBounds(
292
+ Add(self.min, other.min),
293
+ Add(self.max, other.max))
294
+ if other is S.Infinity and self.min is S.NegativeInfinity or \
295
+ other is S.NegativeInfinity and self.max is S.Infinity:
296
+ return AccumBounds(-oo, oo)
297
+ elif other.is_extended_real:
298
+ if self.min is S.NegativeInfinity and self.max is S.Infinity:
299
+ return AccumBounds(-oo, oo)
300
+ elif self.min is S.NegativeInfinity:
301
+ return AccumBounds(-oo, self.max + other)
302
+ elif self.max is S.Infinity:
303
+ return AccumBounds(self.min + other, oo)
304
+ else:
305
+ return AccumBounds(Add(self.min, other), Add(self.max, other))
306
+ return Add(self, other, evaluate=False)
307
+ return NotImplemented
308
+
309
+ __radd__ = __add__
310
+
311
+ def __neg__(self):
312
+ return AccumBounds(-self.max, -self.min)
313
+
314
+ @_sympifyit('other', NotImplemented)
315
+ def __sub__(self, other):
316
+ if isinstance(other, Expr):
317
+ if isinstance(other, AccumBounds):
318
+ return AccumBounds(
319
+ Add(self.min, -other.max),
320
+ Add(self.max, -other.min))
321
+ if other is S.NegativeInfinity and self.min is S.NegativeInfinity or \
322
+ other is S.Infinity and self.max is S.Infinity:
323
+ return AccumBounds(-oo, oo)
324
+ elif other.is_extended_real:
325
+ if self.min is S.NegativeInfinity and self.max is S.Infinity:
326
+ return AccumBounds(-oo, oo)
327
+ elif self.min is S.NegativeInfinity:
328
+ return AccumBounds(-oo, self.max - other)
329
+ elif self.max is S.Infinity:
330
+ return AccumBounds(self.min - other, oo)
331
+ else:
332
+ return AccumBounds(
333
+ Add(self.min, -other),
334
+ Add(self.max, -other))
335
+ return Add(self, -other, evaluate=False)
336
+ return NotImplemented
337
+
338
+ @_sympifyit('other', NotImplemented)
339
+ def __rsub__(self, other):
340
+ return self.__neg__() + other
341
+
342
+ @_sympifyit('other', NotImplemented)
343
+ def __mul__(self, other):
344
+ if self.args == (-oo, oo):
345
+ return self
346
+ if isinstance(other, Expr):
347
+ if isinstance(other, AccumBounds):
348
+ if other.args == (-oo, oo):
349
+ return other
350
+ v = set()
351
+ for a in self.args:
352
+ vi = other*a
353
+ v.update(vi.args or (vi,))
354
+ return AccumBounds(Min(*v), Max(*v))
355
+ if other is S.Infinity:
356
+ if self.min.is_zero:
357
+ return AccumBounds(0, oo)
358
+ if self.max.is_zero:
359
+ return AccumBounds(-oo, 0)
360
+ if other is S.NegativeInfinity:
361
+ if self.min.is_zero:
362
+ return AccumBounds(-oo, 0)
363
+ if self.max.is_zero:
364
+ return AccumBounds(0, oo)
365
+ if other.is_extended_real:
366
+ if other.is_zero:
367
+ if self.max is S.Infinity:
368
+ return AccumBounds(0, oo)
369
+ if self.min is S.NegativeInfinity:
370
+ return AccumBounds(-oo, 0)
371
+ return S.Zero
372
+ if other.is_extended_positive:
373
+ return AccumBounds(
374
+ Mul(self.min, other),
375
+ Mul(self.max, other))
376
+ elif other.is_extended_negative:
377
+ return AccumBounds(
378
+ Mul(self.max, other),
379
+ Mul(self.min, other))
380
+ if isinstance(other, Order):
381
+ return other
382
+ return Mul(self, other, evaluate=False)
383
+ return NotImplemented
384
+
385
+ __rmul__ = __mul__
386
+
387
+ @_sympifyit('other', NotImplemented)
388
+ def __truediv__(self, other):
389
+ if isinstance(other, Expr):
390
+ if isinstance(other, AccumBounds):
391
+ if other.min.is_positive or other.max.is_negative:
392
+ return self * AccumBounds(1/other.max, 1/other.min)
393
+
394
+ if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative and
395
+ other.min.is_extended_nonpositive and other.max.is_extended_nonnegative):
396
+ if self.min.is_zero and other.min.is_zero:
397
+ return AccumBounds(0, oo)
398
+ if self.max.is_zero and other.min.is_zero:
399
+ return AccumBounds(-oo, 0)
400
+ return AccumBounds(-oo, oo)
401
+
402
+ if self.max.is_extended_negative:
403
+ if other.min.is_extended_negative:
404
+ if other.max.is_zero:
405
+ return AccumBounds(self.max / other.min, oo)
406
+ if other.max.is_extended_positive:
407
+ # if we were dealing with intervals we would return
408
+ # Union(Interval(-oo, self.max/other.max),
409
+ # Interval(self.max/other.min, oo))
410
+ return AccumBounds(-oo, oo)
411
+
412
+ if other.min.is_zero and other.max.is_extended_positive:
413
+ return AccumBounds(-oo, self.max / other.max)
414
+
415
+ if self.min.is_extended_positive:
416
+ if other.min.is_extended_negative:
417
+ if other.max.is_zero:
418
+ return AccumBounds(-oo, self.min / other.min)
419
+ if other.max.is_extended_positive:
420
+ # if we were dealing with intervals we would return
421
+ # Union(Interval(-oo, self.min/other.min),
422
+ # Interval(self.min/other.max, oo))
423
+ return AccumBounds(-oo, oo)
424
+
425
+ if other.min.is_zero and other.max.is_extended_positive:
426
+ return AccumBounds(self.min / other.max, oo)
427
+
428
+ elif other.is_extended_real:
429
+ if other in (S.Infinity, S.NegativeInfinity):
430
+ if self == AccumBounds(-oo, oo):
431
+ return AccumBounds(-oo, oo)
432
+ if self.max is S.Infinity:
433
+ return AccumBounds(Min(0, other), Max(0, other))
434
+ if self.min is S.NegativeInfinity:
435
+ return AccumBounds(Min(0, -other), Max(0, -other))
436
+ if other.is_extended_positive:
437
+ return AccumBounds(self.min / other, self.max / other)
438
+ elif other.is_extended_negative:
439
+ return AccumBounds(self.max / other, self.min / other)
440
+ if (1 / other) is S.ComplexInfinity:
441
+ return Mul(self, 1 / other, evaluate=False)
442
+ else:
443
+ return Mul(self, 1 / other)
444
+
445
+ return NotImplemented
446
+
447
+ @_sympifyit('other', NotImplemented)
448
+ def __rtruediv__(self, other):
449
+ if isinstance(other, Expr):
450
+ if other.is_extended_real:
451
+ if other.is_zero:
452
+ return S.Zero
453
+ if (self.min.is_extended_nonpositive and self.max.is_extended_nonnegative):
454
+ if self.min.is_zero:
455
+ if other.is_extended_positive:
456
+ return AccumBounds(Mul(other, 1 / self.max), oo)
457
+ if other.is_extended_negative:
458
+ return AccumBounds(-oo, Mul(other, 1 / self.max))
459
+ if self.max.is_zero:
460
+ if other.is_extended_positive:
461
+ return AccumBounds(-oo, Mul(other, 1 / self.min))
462
+ if other.is_extended_negative:
463
+ return AccumBounds(Mul(other, 1 / self.min), oo)
464
+ return AccumBounds(-oo, oo)
465
+ else:
466
+ return AccumBounds(Min(other / self.min, other / self.max),
467
+ Max(other / self.min, other / self.max))
468
+ return Mul(other, 1 / self, evaluate=False)
469
+ else:
470
+ return NotImplemented
471
+
472
+ @_sympifyit('other', NotImplemented)
473
+ def __pow__(self, other):
474
+ if isinstance(other, Expr):
475
+ if other is S.Infinity:
476
+ if self.min.is_extended_nonnegative:
477
+ if self.max < 1:
478
+ return S.Zero
479
+ if self.min > 1:
480
+ return S.Infinity
481
+ return AccumBounds(0, oo)
482
+ elif self.max.is_extended_negative:
483
+ if self.min > -1:
484
+ return S.Zero
485
+ if self.max < -1:
486
+ return zoo
487
+ return S.NaN
488
+ else:
489
+ if self.min > -1:
490
+ if self.max < 1:
491
+ return S.Zero
492
+ return AccumBounds(0, oo)
493
+ return AccumBounds(-oo, oo)
494
+
495
+ if other is S.NegativeInfinity:
496
+ return (1/self)**oo
497
+
498
+ # generically true
499
+ if (self.max - self.min).is_nonnegative:
500
+ # well defined
501
+ if self.min.is_nonnegative:
502
+ # no 0 to worry about
503
+ if other.is_nonnegative:
504
+ # no infinity to worry about
505
+ return self.func(self.min**other, self.max**other)
506
+
507
+ if other.is_zero:
508
+ return S.One # x**0 = 1
509
+
510
+ if other.is_Integer or other.is_integer:
511
+ if self.min.is_extended_positive:
512
+ return AccumBounds(
513
+ Min(self.min**other, self.max**other),
514
+ Max(self.min**other, self.max**other))
515
+ elif self.max.is_extended_negative:
516
+ return AccumBounds(
517
+ Min(self.max**other, self.min**other),
518
+ Max(self.max**other, self.min**other))
519
+
520
+ if other % 2 == 0:
521
+ if other.is_extended_negative:
522
+ if self.min.is_zero:
523
+ return AccumBounds(self.max**other, oo)
524
+ if self.max.is_zero:
525
+ return AccumBounds(self.min**other, oo)
526
+ return (1/self)**(-other)
527
+ return AccumBounds(
528
+ S.Zero, Max(self.min**other, self.max**other))
529
+ elif other % 2 == 1:
530
+ if other.is_extended_negative:
531
+ if self.min.is_zero:
532
+ return AccumBounds(self.max**other, oo)
533
+ if self.max.is_zero:
534
+ return AccumBounds(-oo, self.min**other)
535
+ return (1/self)**(-other)
536
+ return AccumBounds(self.min**other, self.max**other)
537
+
538
+ # non-integer exponent
539
+ # 0**neg or neg**frac yields complex
540
+ if (other.is_number or other.is_rational) and (
541
+ self.min.is_extended_nonnegative or (
542
+ other.is_extended_nonnegative and
543
+ self.min.is_extended_nonnegative)):
544
+ num, den = other.as_numer_denom()
545
+ if num is S.One:
546
+ return AccumBounds(*[i**(1/den) for i in self.args])
547
+
548
+ elif den is not S.One: # e.g. if other is not Float
549
+ return (self**num)**(1/den) # ok for non-negative base
550
+
551
+ if isinstance(other, AccumBounds):
552
+ if (self.min.is_extended_positive or
553
+ self.min.is_extended_nonnegative and
554
+ other.min.is_extended_nonnegative):
555
+ p = [self**i for i in other.args]
556
+ if not any(i.is_Pow for i in p):
557
+ a = [j for i in p for j in i.args or (i,)]
558
+ try:
559
+ return self.func(min(a), max(a))
560
+ except TypeError: # can't sort
561
+ pass
562
+
563
+ return Pow(self, other, evaluate=False)
564
+
565
+ return NotImplemented
566
+
567
+ @_sympifyit('other', NotImplemented)
568
+ def __rpow__(self, other):
569
+ if other.is_real and other.is_extended_nonnegative and (
570
+ self.max - self.min).is_extended_positive:
571
+ if other is S.One:
572
+ return S.One
573
+ if other.is_extended_positive:
574
+ a, b = [other**i for i in self.args]
575
+ if min(a, b) != a:
576
+ a, b = b, a
577
+ return self.func(a, b)
578
+ if other.is_zero:
579
+ if self.min.is_zero:
580
+ return self.func(0, 1)
581
+ if self.min.is_extended_positive:
582
+ return S.Zero
583
+
584
+ return Pow(other, self, evaluate=False)
585
+
586
+ def __abs__(self):
587
+ if self.max.is_extended_negative:
588
+ return self.__neg__()
589
+ elif self.min.is_extended_negative:
590
+ return AccumBounds(S.Zero, Max(abs(self.min), self.max))
591
+ else:
592
+ return self
593
+
594
+
595
+ def __contains__(self, other):
596
+ """
597
+ Returns ``True`` if other is contained in self, where other
598
+ belongs to extended real numbers, ``False`` if not contained,
599
+ otherwise TypeError is raised.
600
+
601
+ Examples
602
+ ========
603
+
604
+ >>> from sympy import AccumBounds, oo
605
+ >>> 1 in AccumBounds(-1, 3)
606
+ True
607
+
608
+ -oo and oo go together as limits (in AccumulationBounds).
609
+
610
+ >>> -oo in AccumBounds(1, oo)
611
+ True
612
+
613
+ >>> oo in AccumBounds(-oo, 0)
614
+ True
615
+
616
+ """
617
+ other = _sympify(other)
618
+
619
+ if other in (S.Infinity, S.NegativeInfinity):
620
+ if self.min is S.NegativeInfinity or self.max is S.Infinity:
621
+ return True
622
+ return False
623
+
624
+ rv = And(self.min <= other, self.max >= other)
625
+ if rv not in (True, False):
626
+ raise TypeError("input failed to evaluate")
627
+ return rv
628
+
629
+ def intersection(self, other):
630
+ """
631
+ Returns the intersection of 'self' and 'other'.
632
+ Here other can be an instance of :py:class:`~.FiniteSet` or AccumulationBounds.
633
+
634
+ Parameters
635
+ ==========
636
+
637
+ other : AccumulationBounds
638
+ Another AccumulationBounds object with which the intersection
639
+ has to be computed.
640
+
641
+ Returns
642
+ =======
643
+
644
+ AccumulationBounds
645
+ Intersection of ``self`` and ``other``.
646
+
647
+ Examples
648
+ ========
649
+
650
+ >>> from sympy import AccumBounds, FiniteSet
651
+ >>> AccumBounds(1, 3).intersection(AccumBounds(2, 4))
652
+ AccumBounds(2, 3)
653
+
654
+ >>> AccumBounds(1, 3).intersection(AccumBounds(4, 6))
655
+ EmptySet
656
+
657
+ >>> AccumBounds(1, 4).intersection(FiniteSet(1, 2, 5))
658
+ {1, 2}
659
+
660
+ """
661
+ if not isinstance(other, (AccumBounds, FiniteSet)):
662
+ raise TypeError(
663
+ "Input must be AccumulationBounds or FiniteSet object")
664
+
665
+ if isinstance(other, FiniteSet):
666
+ fin_set = S.EmptySet
667
+ for i in other:
668
+ if i in self:
669
+ fin_set = fin_set + FiniteSet(i)
670
+ return fin_set
671
+
672
+ if self.max < other.min or self.min > other.max:
673
+ return S.EmptySet
674
+
675
+ if self.min <= other.min:
676
+ if self.max <= other.max:
677
+ return AccumBounds(other.min, self.max)
678
+ if self.max > other.max:
679
+ return other
680
+
681
+ if other.min <= self.min:
682
+ if other.max < self.max:
683
+ return AccumBounds(self.min, other.max)
684
+ if other.max > self.max:
685
+ return self
686
+
687
+ def union(self, other):
688
+ # TODO : Devise a better method for Union of AccumBounds
689
+ # this method is not actually correct and
690
+ # can be made better
691
+ if not isinstance(other, AccumBounds):
692
+ raise TypeError(
693
+ "Input must be AccumulationBounds or FiniteSet object")
694
+
695
+ if self.min <= other.min and self.max >= other.min:
696
+ return AccumBounds(self.min, Max(self.max, other.max))
697
+
698
+ if other.min <= self.min and other.max >= self.min:
699
+ return AccumBounds(other.min, Max(self.max, other.max))
700
+
701
+
702
+ @dispatch(AccumulationBounds, AccumulationBounds) # type: ignore # noqa:F811
703
+ def _eval_is_le(lhs, rhs): # noqa:F811
704
+ if is_le(lhs.max, rhs.min):
705
+ return True
706
+ if is_gt(lhs.min, rhs.max):
707
+ return False
708
+
709
+
710
+ @dispatch(AccumulationBounds, Basic) # type: ignore # noqa:F811
711
+ def _eval_is_le(lhs, rhs): # noqa: F811
712
+
713
+ """
714
+ Returns ``True `` if range of values attained by ``lhs`` AccumulationBounds
715
+ object is greater than the range of values attained by ``rhs``,
716
+ where ``rhs`` may be any value of type AccumulationBounds object or
717
+ extended real number value, ``False`` if ``rhs`` satisfies
718
+ the same property, else an unevaluated :py:class:`~.Relational`.
719
+
720
+ Examples
721
+ ========
722
+
723
+ >>> from sympy import AccumBounds, oo
724
+ >>> AccumBounds(1, 3) > AccumBounds(4, oo)
725
+ False
726
+ >>> AccumBounds(1, 4) > AccumBounds(3, 4)
727
+ AccumBounds(1, 4) > AccumBounds(3, 4)
728
+ >>> AccumBounds(1, oo) > -1
729
+ True
730
+
731
+ """
732
+ if not rhs.is_extended_real:
733
+ raise TypeError(
734
+ "Invalid comparison of %s %s" %
735
+ (type(rhs), rhs))
736
+ elif rhs.is_comparable:
737
+ if is_le(lhs.max, rhs):
738
+ return True
739
+ if is_gt(lhs.min, rhs):
740
+ return False
741
+
742
+
743
+ @dispatch(AccumulationBounds, AccumulationBounds)
744
+ def _eval_is_ge(lhs, rhs): # noqa:F811
745
+ if is_ge(lhs.min, rhs.max):
746
+ return True
747
+ if is_lt(lhs.max, rhs.min):
748
+ return False
749
+
750
+
751
+ @dispatch(AccumulationBounds, Expr) # type:ignore
752
+ def _eval_is_ge(lhs, rhs): # noqa: F811
753
+ """
754
+ Returns ``True`` if range of values attained by ``lhs`` AccumulationBounds
755
+ object is less that the range of values attained by ``rhs``, where
756
+ other may be any value of type AccumulationBounds object or extended
757
+ real number value, ``False`` if ``rhs`` satisfies the same
758
+ property, else an unevaluated :py:class:`~.Relational`.
759
+
760
+ Examples
761
+ ========
762
+
763
+ >>> from sympy import AccumBounds, oo
764
+ >>> AccumBounds(1, 3) >= AccumBounds(4, oo)
765
+ False
766
+ >>> AccumBounds(1, 4) >= AccumBounds(3, 4)
767
+ AccumBounds(1, 4) >= AccumBounds(3, 4)
768
+ >>> AccumBounds(1, oo) >= 1
769
+ True
770
+ """
771
+
772
+ if not rhs.is_extended_real:
773
+ raise TypeError(
774
+ "Invalid comparison of %s %s" %
775
+ (type(rhs), rhs))
776
+ elif rhs.is_comparable:
777
+ if is_ge(lhs.min, rhs):
778
+ return True
779
+ if is_lt(lhs.max, rhs):
780
+ return False
781
+
782
+
783
+ @dispatch(Expr, AccumulationBounds) # type:ignore
784
+ def _eval_is_ge(lhs, rhs): # noqa:F811
785
+ if not lhs.is_extended_real:
786
+ raise TypeError(
787
+ "Invalid comparison of %s %s" %
788
+ (type(lhs), lhs))
789
+ elif lhs.is_comparable:
790
+ if is_le(rhs.max, lhs):
791
+ return True
792
+ if is_gt(rhs.min, lhs):
793
+ return False
794
+
795
+
796
+ @dispatch(AccumulationBounds, AccumulationBounds) # type:ignore
797
+ def _eval_is_ge(lhs, rhs): # noqa:F811
798
+ if is_ge(lhs.min, rhs.max):
799
+ return True
800
+ if is_lt(lhs.max, rhs.min):
801
+ return False
802
+
803
+ # setting an alias for AccumulationBounds
804
+ AccumBounds = AccumulationBounds
lib/python3.12/site-packages/sympy/calculus/euler.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module implements a method to find
3
+ Euler-Lagrange Equations for given Lagrangian.
4
+ """
5
+ from itertools import combinations_with_replacement
6
+ from sympy.core.function import (Derivative, Function, diff)
7
+ from sympy.core.relational import Eq
8
+ from sympy.core.singleton import S
9
+ from sympy.core.symbol import Symbol
10
+ from sympy.core.sympify import sympify
11
+ from sympy.utilities.iterables import iterable
12
+
13
+
14
+ def euler_equations(L, funcs=(), vars=()):
15
+ r"""
16
+ Find the Euler-Lagrange equations [1]_ for a given Lagrangian.
17
+
18
+ Parameters
19
+ ==========
20
+
21
+ L : Expr
22
+ The Lagrangian that should be a function of the functions listed
23
+ in the second argument and their derivatives.
24
+
25
+ For example, in the case of two functions $f(x,y)$, $g(x,y)$ and
26
+ two independent variables $x$, $y$ the Lagrangian has the form:
27
+
28
+ .. math:: L\left(f(x,y),g(x,y),\frac{\partial f(x,y)}{\partial x},
29
+ \frac{\partial f(x,y)}{\partial y},
30
+ \frac{\partial g(x,y)}{\partial x},
31
+ \frac{\partial g(x,y)}{\partial y},x,y\right)
32
+
33
+ In many cases it is not necessary to provide anything, except the
34
+ Lagrangian, it will be auto-detected (and an error raised if this
35
+ cannot be done).
36
+
37
+ funcs : Function or an iterable of Functions
38
+ The functions that the Lagrangian depends on. The Euler equations
39
+ are differential equations for each of these functions.
40
+
41
+ vars : Symbol or an iterable of Symbols
42
+ The Symbols that are the independent variables of the functions.
43
+
44
+ Returns
45
+ =======
46
+
47
+ eqns : list of Eq
48
+ The list of differential equations, one for each function.
49
+
50
+ Examples
51
+ ========
52
+
53
+ >>> from sympy import euler_equations, Symbol, Function
54
+ >>> x = Function('x')
55
+ >>> t = Symbol('t')
56
+ >>> L = (x(t).diff(t))**2/2 - x(t)**2/2
57
+ >>> euler_equations(L, x(t), t)
58
+ [Eq(-x(t) - Derivative(x(t), (t, 2)), 0)]
59
+ >>> u = Function('u')
60
+ >>> x = Symbol('x')
61
+ >>> L = (u(t, x).diff(t))**2/2 - (u(t, x).diff(x))**2/2
62
+ >>> euler_equations(L, u(t, x), [t, x])
63
+ [Eq(-Derivative(u(t, x), (t, 2)) + Derivative(u(t, x), (x, 2)), 0)]
64
+
65
+ References
66
+ ==========
67
+
68
+ .. [1] https://en.wikipedia.org/wiki/Euler%E2%80%93Lagrange_equation
69
+
70
+ """
71
+
72
+ funcs = tuple(funcs) if iterable(funcs) else (funcs,)
73
+
74
+ if not funcs:
75
+ funcs = tuple(L.atoms(Function))
76
+ else:
77
+ for f in funcs:
78
+ if not isinstance(f, Function):
79
+ raise TypeError('Function expected, got: %s' % f)
80
+
81
+ vars = tuple(vars) if iterable(vars) else (vars,)
82
+
83
+ if not vars:
84
+ vars = funcs[0].args
85
+ else:
86
+ vars = tuple(sympify(var) for var in vars)
87
+
88
+ if not all(isinstance(v, Symbol) for v in vars):
89
+ raise TypeError('Variables are not symbols, got %s' % vars)
90
+
91
+ for f in funcs:
92
+ if not vars == f.args:
93
+ raise ValueError("Variables %s do not match args: %s" % (vars, f))
94
+
95
+ order = max([len(d.variables) for d in L.atoms(Derivative)
96
+ if d.expr in funcs] + [0])
97
+
98
+ eqns = []
99
+ for f in funcs:
100
+ eq = diff(L, f)
101
+ for i in range(1, order + 1):
102
+ for p in combinations_with_replacement(vars, i):
103
+ eq = eq + S.NegativeOne**i*diff(L, diff(f, *p), *p)
104
+ new_eq = Eq(eq, 0)
105
+ if isinstance(new_eq, Eq):
106
+ eqns.append(new_eq)
107
+
108
+ return eqns
lib/python3.12/site-packages/sympy/calculus/finite_diff.py ADDED
@@ -0,0 +1,476 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Finite difference weights
3
+ =========================
4
+
5
+ This module implements an algorithm for efficient generation of finite
6
+ difference weights for ordinary differentials of functions for
7
+ derivatives from 0 (interpolation) up to arbitrary order.
8
+
9
+ The core algorithm is provided in the finite difference weight generating
10
+ function (``finite_diff_weights``), and two convenience functions are provided
11
+ for:
12
+
13
+ - estimating a derivative (or interpolate) directly from a series of points
14
+ is also provided (``apply_finite_diff``).
15
+ - differentiating by using finite difference approximations
16
+ (``differentiate_finite``).
17
+
18
+ """
19
+
20
+ from sympy.core.function import Derivative
21
+ from sympy.core.singleton import S
22
+ from sympy.core.function import Subs
23
+ from sympy.core.traversal import preorder_traversal
24
+ from sympy.utilities.exceptions import sympy_deprecation_warning
25
+ from sympy.utilities.iterables import iterable
26
+
27
+
28
+
29
+ def finite_diff_weights(order, x_list, x0=S.One):
30
+ """
31
+ Calculates the finite difference weights for an arbitrarily spaced
32
+ one-dimensional grid (``x_list``) for derivatives at ``x0`` of order
33
+ 0, 1, ..., up to ``order`` using a recursive formula. Order of accuracy
34
+ is at least ``len(x_list) - order``, if ``x_list`` is defined correctly.
35
+
36
+ Parameters
37
+ ==========
38
+
39
+ order: int
40
+ Up to what derivative order weights should be calculated.
41
+ 0 corresponds to interpolation.
42
+ x_list: sequence
43
+ Sequence of (unique) values for the independent variable.
44
+ It is useful (but not necessary) to order ``x_list`` from
45
+ nearest to furthest from ``x0``; see examples below.
46
+ x0: Number or Symbol
47
+ Root or value of the independent variable for which the finite
48
+ difference weights should be generated. Default is ``S.One``.
49
+
50
+ Returns
51
+ =======
52
+
53
+ list
54
+ A list of sublists, each corresponding to coefficients for
55
+ increasing derivative order, and each containing lists of
56
+ coefficients for increasing subsets of x_list.
57
+
58
+ Examples
59
+ ========
60
+
61
+ >>> from sympy import finite_diff_weights, S
62
+ >>> res = finite_diff_weights(1, [-S(1)/2, S(1)/2, S(3)/2, S(5)/2], 0)
63
+ >>> res
64
+ [[[1, 0, 0, 0],
65
+ [1/2, 1/2, 0, 0],
66
+ [3/8, 3/4, -1/8, 0],
67
+ [5/16, 15/16, -5/16, 1/16]],
68
+ [[0, 0, 0, 0],
69
+ [-1, 1, 0, 0],
70
+ [-1, 1, 0, 0],
71
+ [-23/24, 7/8, 1/8, -1/24]]]
72
+ >>> res[0][-1] # FD weights for 0th derivative, using full x_list
73
+ [5/16, 15/16, -5/16, 1/16]
74
+ >>> res[1][-1] # FD weights for 1st derivative
75
+ [-23/24, 7/8, 1/8, -1/24]
76
+ >>> res[1][-2] # FD weights for 1st derivative, using x_list[:-1]
77
+ [-1, 1, 0, 0]
78
+ >>> res[1][-1][0] # FD weight for 1st deriv. for x_list[0]
79
+ -23/24
80
+ >>> res[1][-1][1] # FD weight for 1st deriv. for x_list[1], etc.
81
+ 7/8
82
+
83
+ Each sublist contains the most accurate formula at the end.
84
+ Note, that in the above example ``res[1][1]`` is the same as ``res[1][2]``.
85
+ Since res[1][2] has an order of accuracy of
86
+ ``len(x_list[:3]) - order = 3 - 1 = 2``, the same is true for ``res[1][1]``!
87
+
88
+ >>> res = finite_diff_weights(1, [S(0), S(1), -S(1), S(2), -S(2)], 0)[1]
89
+ >>> res
90
+ [[0, 0, 0, 0, 0],
91
+ [-1, 1, 0, 0, 0],
92
+ [0, 1/2, -1/2, 0, 0],
93
+ [-1/2, 1, -1/3, -1/6, 0],
94
+ [0, 2/3, -2/3, -1/12, 1/12]]
95
+ >>> res[0] # no approximation possible, using x_list[0] only
96
+ [0, 0, 0, 0, 0]
97
+ >>> res[1] # classic forward step approximation
98
+ [-1, 1, 0, 0, 0]
99
+ >>> res[2] # classic centered approximation
100
+ [0, 1/2, -1/2, 0, 0]
101
+ >>> res[3:] # higher order approximations
102
+ [[-1/2, 1, -1/3, -1/6, 0], [0, 2/3, -2/3, -1/12, 1/12]]
103
+
104
+ Let us compare this to a differently defined ``x_list``. Pay attention to
105
+ ``foo[i][k]`` corresponding to the gridpoint defined by ``x_list[k]``.
106
+
107
+ >>> foo = finite_diff_weights(1, [-S(2), -S(1), S(0), S(1), S(2)], 0)[1]
108
+ >>> foo
109
+ [[0, 0, 0, 0, 0],
110
+ [-1, 1, 0, 0, 0],
111
+ [1/2, -2, 3/2, 0, 0],
112
+ [1/6, -1, 1/2, 1/3, 0],
113
+ [1/12, -2/3, 0, 2/3, -1/12]]
114
+ >>> foo[1] # not the same and of lower accuracy as res[1]!
115
+ [-1, 1, 0, 0, 0]
116
+ >>> foo[2] # classic double backward step approximation
117
+ [1/2, -2, 3/2, 0, 0]
118
+ >>> foo[4] # the same as res[4]
119
+ [1/12, -2/3, 0, 2/3, -1/12]
120
+
121
+ Note that, unless you plan on using approximations based on subsets of
122
+ ``x_list``, the order of gridpoints does not matter.
123
+
124
+ The capability to generate weights at arbitrary points can be
125
+ used e.g. to minimize Runge's phenomenon by using Chebyshev nodes:
126
+
127
+ >>> from sympy import cos, symbols, pi, simplify
128
+ >>> N, (h, x) = 4, symbols('h x')
129
+ >>> x_list = [x+h*cos(i*pi/(N)) for i in range(N,-1,-1)] # chebyshev nodes
130
+ >>> print(x_list)
131
+ [-h + x, -sqrt(2)*h/2 + x, x, sqrt(2)*h/2 + x, h + x]
132
+ >>> mycoeffs = finite_diff_weights(1, x_list, 0)[1][4]
133
+ >>> [simplify(c) for c in mycoeffs] #doctest: +NORMALIZE_WHITESPACE
134
+ [(h**3/2 + h**2*x - 3*h*x**2 - 4*x**3)/h**4,
135
+ (-sqrt(2)*h**3 - 4*h**2*x + 3*sqrt(2)*h*x**2 + 8*x**3)/h**4,
136
+ (6*h**2*x - 8*x**3)/h**4,
137
+ (sqrt(2)*h**3 - 4*h**2*x - 3*sqrt(2)*h*x**2 + 8*x**3)/h**4,
138
+ (-h**3/2 + h**2*x + 3*h*x**2 - 4*x**3)/h**4]
139
+
140
+ Notes
141
+ =====
142
+
143
+ If weights for a finite difference approximation of 3rd order
144
+ derivative is wanted, weights for 0th, 1st and 2nd order are
145
+ calculated "for free", so are formulae using subsets of ``x_list``.
146
+ This is something one can take advantage of to save computational cost.
147
+ Be aware that one should define ``x_list`` from nearest to furthest from
148
+ ``x0``. If not, subsets of ``x_list`` will yield poorer approximations,
149
+ which might not grand an order of accuracy of ``len(x_list) - order``.
150
+
151
+ See also
152
+ ========
153
+
154
+ sympy.calculus.finite_diff.apply_finite_diff
155
+
156
+ References
157
+ ==========
158
+
159
+ .. [1] Generation of Finite Difference Formulas on Arbitrarily Spaced
160
+ Grids, Bengt Fornberg; Mathematics of computation; 51; 184;
161
+ (1988); 699-706; doi:10.1090/S0025-5718-1988-0935077-0
162
+
163
+ """
164
+ # The notation below closely corresponds to the one used in the paper.
165
+ order = S(order)
166
+ if not order.is_number:
167
+ raise ValueError("Cannot handle symbolic order.")
168
+ if order < 0:
169
+ raise ValueError("Negative derivative order illegal.")
170
+ if int(order) != order:
171
+ raise ValueError("Non-integer order illegal")
172
+ M = order
173
+ N = len(x_list) - 1
174
+ delta = [[[0 for nu in range(N+1)] for n in range(N+1)] for
175
+ m in range(M+1)]
176
+ delta[0][0][0] = S.One
177
+ c1 = S.One
178
+ for n in range(1, N+1):
179
+ c2 = S.One
180
+ for nu in range(n):
181
+ c3 = x_list[n] - x_list[nu]
182
+ c2 = c2 * c3
183
+ if n <= M:
184
+ delta[n][n-1][nu] = 0
185
+ for m in range(min(n, M)+1):
186
+ delta[m][n][nu] = (x_list[n]-x0)*delta[m][n-1][nu] -\
187
+ m*delta[m-1][n-1][nu]
188
+ delta[m][n][nu] /= c3
189
+ for m in range(min(n, M)+1):
190
+ delta[m][n][n] = c1/c2*(m*delta[m-1][n-1][n-1] -
191
+ (x_list[n-1]-x0)*delta[m][n-1][n-1])
192
+ c1 = c2
193
+ return delta
194
+
195
+
196
+ def apply_finite_diff(order, x_list, y_list, x0=S.Zero):
197
+ """
198
+ Calculates the finite difference approximation of
199
+ the derivative of requested order at ``x0`` from points
200
+ provided in ``x_list`` and ``y_list``.
201
+
202
+ Parameters
203
+ ==========
204
+
205
+ order: int
206
+ order of derivative to approximate. 0 corresponds to interpolation.
207
+ x_list: sequence
208
+ Sequence of (unique) values for the independent variable.
209
+ y_list: sequence
210
+ The function value at corresponding values for the independent
211
+ variable in x_list.
212
+ x0: Number or Symbol
213
+ At what value of the independent variable the derivative should be
214
+ evaluated. Defaults to 0.
215
+
216
+ Returns
217
+ =======
218
+
219
+ sympy.core.add.Add or sympy.core.numbers.Number
220
+ The finite difference expression approximating the requested
221
+ derivative order at ``x0``.
222
+
223
+ Examples
224
+ ========
225
+
226
+ >>> from sympy import apply_finite_diff
227
+ >>> cube = lambda arg: (1.0*arg)**3
228
+ >>> xlist = range(-3,3+1)
229
+ >>> apply_finite_diff(2, xlist, map(cube, xlist), 2) - 12 # doctest: +SKIP
230
+ -3.55271367880050e-15
231
+
232
+ we see that the example above only contain rounding errors.
233
+ apply_finite_diff can also be used on more abstract objects:
234
+
235
+ >>> from sympy import IndexedBase, Idx
236
+ >>> x, y = map(IndexedBase, 'xy')
237
+ >>> i = Idx('i')
238
+ >>> x_list, y_list = zip(*[(x[i+j], y[i+j]) for j in range(-1,2)])
239
+ >>> apply_finite_diff(1, x_list, y_list, x[i])
240
+ ((x[i + 1] - x[i])/(-x[i - 1] + x[i]) - 1)*y[i]/(x[i + 1] - x[i]) -
241
+ (x[i + 1] - x[i])*y[i - 1]/((x[i + 1] - x[i - 1])*(-x[i - 1] + x[i])) +
242
+ (-x[i - 1] + x[i])*y[i + 1]/((x[i + 1] - x[i - 1])*(x[i + 1] - x[i]))
243
+
244
+ Notes
245
+ =====
246
+
247
+ Order = 0 corresponds to interpolation.
248
+ Only supply so many points you think makes sense
249
+ to around x0 when extracting the derivative (the function
250
+ need to be well behaved within that region). Also beware
251
+ of Runge's phenomenon.
252
+
253
+ See also
254
+ ========
255
+
256
+ sympy.calculus.finite_diff.finite_diff_weights
257
+
258
+ References
259
+ ==========
260
+
261
+ Fortran 90 implementation with Python interface for numerics: finitediff_
262
+
263
+ .. _finitediff: https://github.com/bjodah/finitediff
264
+
265
+ """
266
+
267
+ # In the original paper the following holds for the notation:
268
+ # M = order
269
+ # N = len(x_list) - 1
270
+
271
+ N = len(x_list) - 1
272
+ if len(x_list) != len(y_list):
273
+ raise ValueError("x_list and y_list not equal in length.")
274
+
275
+ delta = finite_diff_weights(order, x_list, x0)
276
+
277
+ derivative = 0
278
+ for nu in range(len(x_list)):
279
+ derivative += delta[order][N][nu]*y_list[nu]
280
+ return derivative
281
+
282
+
283
+ def _as_finite_diff(derivative, points=1, x0=None, wrt=None):
284
+ """
285
+ Returns an approximation of a derivative of a function in
286
+ the form of a finite difference formula. The expression is a
287
+ weighted sum of the function at a number of discrete values of
288
+ (one of) the independent variable(s).
289
+
290
+ Parameters
291
+ ==========
292
+
293
+ derivative: a Derivative instance
294
+
295
+ points: sequence or coefficient, optional
296
+ If sequence: discrete values (length >= order+1) of the
297
+ independent variable used for generating the finite
298
+ difference weights.
299
+ If it is a coefficient, it will be used as the step-size
300
+ for generating an equidistant sequence of length order+1
301
+ centered around ``x0``. default: 1 (step-size 1)
302
+
303
+ x0: number or Symbol, optional
304
+ the value of the independent variable (``wrt``) at which the
305
+ derivative is to be approximated. Default: same as ``wrt``.
306
+
307
+ wrt: Symbol, optional
308
+ "with respect to" the variable for which the (partial)
309
+ derivative is to be approximated for. If not provided it
310
+ is required that the Derivative is ordinary. Default: ``None``.
311
+
312
+ Examples
313
+ ========
314
+
315
+ >>> from sympy import symbols, Function, exp, sqrt, Symbol
316
+ >>> from sympy.calculus.finite_diff import _as_finite_diff
317
+ >>> x, h = symbols('x h')
318
+ >>> f = Function('f')
319
+ >>> _as_finite_diff(f(x).diff(x))
320
+ -f(x - 1/2) + f(x + 1/2)
321
+
322
+ The default step size and number of points are 1 and ``order + 1``
323
+ respectively. We can change the step size by passing a symbol
324
+ as a parameter:
325
+
326
+ >>> _as_finite_diff(f(x).diff(x), h)
327
+ -f(-h/2 + x)/h + f(h/2 + x)/h
328
+
329
+ We can also specify the discretized values to be used in a sequence:
330
+
331
+ >>> _as_finite_diff(f(x).diff(x), [x, x+h, x+2*h])
332
+ -3*f(x)/(2*h) + 2*f(h + x)/h - f(2*h + x)/(2*h)
333
+
334
+ The algorithm is not restricted to use equidistant spacing, nor
335
+ do we need to make the approximation around ``x0``, but we can get
336
+ an expression estimating the derivative at an offset:
337
+
338
+ >>> e, sq2 = exp(1), sqrt(2)
339
+ >>> xl = [x-h, x+h, x+e*h]
340
+ >>> _as_finite_diff(f(x).diff(x, 1), xl, x+h*sq2)
341
+ 2*h*((h + sqrt(2)*h)/(2*h) - (-sqrt(2)*h + h)/(2*h))*f(E*h + x)/((-h + E*h)*(h + E*h)) +
342
+ (-(-sqrt(2)*h + h)/(2*h) - (-sqrt(2)*h + E*h)/(2*h))*f(-h + x)/(h + E*h) +
343
+ (-(h + sqrt(2)*h)/(2*h) + (-sqrt(2)*h + E*h)/(2*h))*f(h + x)/(-h + E*h)
344
+
345
+ Partial derivatives are also supported:
346
+
347
+ >>> y = Symbol('y')
348
+ >>> d2fdxdy=f(x,y).diff(x,y)
349
+ >>> _as_finite_diff(d2fdxdy, wrt=x)
350
+ -Derivative(f(x - 1/2, y), y) + Derivative(f(x + 1/2, y), y)
351
+
352
+ See also
353
+ ========
354
+
355
+ sympy.calculus.finite_diff.apply_finite_diff
356
+ sympy.calculus.finite_diff.finite_diff_weights
357
+
358
+ """
359
+ if derivative.is_Derivative:
360
+ pass
361
+ elif derivative.is_Atom:
362
+ return derivative
363
+ else:
364
+ return derivative.fromiter(
365
+ [_as_finite_diff(ar, points, x0, wrt) for ar
366
+ in derivative.args], **derivative.assumptions0)
367
+
368
+ if wrt is None:
369
+ old = None
370
+ for v in derivative.variables:
371
+ if old is v:
372
+ continue
373
+ derivative = _as_finite_diff(derivative, points, x0, v)
374
+ old = v
375
+ return derivative
376
+
377
+ order = derivative.variables.count(wrt)
378
+
379
+ if x0 is None:
380
+ x0 = wrt
381
+
382
+ if not iterable(points):
383
+ if getattr(points, 'is_Function', False) and wrt in points.args:
384
+ points = points.subs(wrt, x0)
385
+ # points is simply the step-size, let's make it a
386
+ # equidistant sequence centered around x0
387
+ if order % 2 == 0:
388
+ # even order => odd number of points, grid point included
389
+ points = [x0 + points*i for i
390
+ in range(-order//2, order//2 + 1)]
391
+ else:
392
+ # odd order => even number of points, half-way wrt grid point
393
+ points = [x0 + points*S(i)/2 for i
394
+ in range(-order, order + 1, 2)]
395
+ others = [wrt, 0]
396
+ for v in set(derivative.variables):
397
+ if v == wrt:
398
+ continue
399
+ others += [v, derivative.variables.count(v)]
400
+ if len(points) < order+1:
401
+ raise ValueError("Too few points for order %d" % order)
402
+ return apply_finite_diff(order, points, [
403
+ Derivative(derivative.expr.subs({wrt: x}), *others) for
404
+ x in points], x0)
405
+
406
+
407
+ def differentiate_finite(expr, *symbols,
408
+ points=1, x0=None, wrt=None, evaluate=False):
409
+ r""" Differentiate expr and replace Derivatives with finite differences.
410
+
411
+ Parameters
412
+ ==========
413
+
414
+ expr : expression
415
+ \*symbols : differentiate with respect to symbols
416
+ points: sequence, coefficient or undefined function, optional
417
+ see ``Derivative.as_finite_difference``
418
+ x0: number or Symbol, optional
419
+ see ``Derivative.as_finite_difference``
420
+ wrt: Symbol, optional
421
+ see ``Derivative.as_finite_difference``
422
+
423
+ Examples
424
+ ========
425
+
426
+ >>> from sympy import sin, Function, differentiate_finite
427
+ >>> from sympy.abc import x, y, h
428
+ >>> f, g = Function('f'), Function('g')
429
+ >>> differentiate_finite(f(x)*g(x), x, points=[x-h, x+h])
430
+ -f(-h + x)*g(-h + x)/(2*h) + f(h + x)*g(h + x)/(2*h)
431
+
432
+ ``differentiate_finite`` works on any expression, including the expressions
433
+ with embedded derivatives:
434
+
435
+ >>> differentiate_finite(f(x) + sin(x), x, 2)
436
+ -2*f(x) + f(x - 1) + f(x + 1) - 2*sin(x) + sin(x - 1) + sin(x + 1)
437
+ >>> differentiate_finite(f(x, y), x, y)
438
+ f(x - 1/2, y - 1/2) - f(x - 1/2, y + 1/2) - f(x + 1/2, y - 1/2) + f(x + 1/2, y + 1/2)
439
+ >>> differentiate_finite(f(x)*g(x).diff(x), x)
440
+ (-g(x) + g(x + 1))*f(x + 1/2) - (g(x) - g(x - 1))*f(x - 1/2)
441
+
442
+ To make finite difference with non-constant discretization step use
443
+ undefined functions:
444
+
445
+ >>> dx = Function('dx')
446
+ >>> differentiate_finite(f(x)*g(x).diff(x), points=dx(x))
447
+ -(-g(x - dx(x)/2 - dx(x - dx(x)/2)/2)/dx(x - dx(x)/2) +
448
+ g(x - dx(x)/2 + dx(x - dx(x)/2)/2)/dx(x - dx(x)/2))*f(x - dx(x)/2)/dx(x) +
449
+ (-g(x + dx(x)/2 - dx(x + dx(x)/2)/2)/dx(x + dx(x)/2) +
450
+ g(x + dx(x)/2 + dx(x + dx(x)/2)/2)/dx(x + dx(x)/2))*f(x + dx(x)/2)/dx(x)
451
+
452
+ """
453
+ if any(term.is_Derivative for term in list(preorder_traversal(expr))):
454
+ evaluate = False
455
+
456
+ Dexpr = expr.diff(*symbols, evaluate=evaluate)
457
+ if evaluate:
458
+ sympy_deprecation_warning("""
459
+ The evaluate flag to differentiate_finite() is deprecated.
460
+
461
+ evaluate=True expands the intermediate derivatives before computing
462
+ differences, but this usually not what you want, as it does not
463
+ satisfy the product rule.
464
+ """,
465
+ deprecated_since_version="1.5",
466
+ active_deprecations_target="deprecated-differentiate_finite-evaluate",
467
+ )
468
+ return Dexpr.replace(
469
+ lambda arg: arg.is_Derivative,
470
+ lambda arg: arg.as_finite_difference(points=points, x0=x0, wrt=wrt))
471
+ else:
472
+ DFexpr = Dexpr.as_finite_difference(points=points, x0=x0, wrt=wrt)
473
+ return DFexpr.replace(
474
+ lambda arg: isinstance(arg, Subs),
475
+ lambda arg: arg.expr.as_finite_difference(
476
+ points=points, x0=arg.point[0], wrt=arg.variables[0]))
lib/python3.12/site-packages/sympy/calculus/singularities.py ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Singularities
3
+ =============
4
+
5
+ This module implements algorithms for finding singularities for a function
6
+ and identifying types of functions.
7
+
8
+ The differential calculus methods in this module include methods to identify
9
+ the following function types in the given ``Interval``:
10
+ - Increasing
11
+ - Strictly Increasing
12
+ - Decreasing
13
+ - Strictly Decreasing
14
+ - Monotonic
15
+
16
+ """
17
+
18
+ from sympy.core.power import Pow
19
+ from sympy.core.singleton import S
20
+ from sympy.core.symbol import Symbol
21
+ from sympy.core.sympify import sympify
22
+ from sympy.functions.elementary.exponential import log
23
+ from sympy.functions.elementary.trigonometric import sec, csc, cot, tan, cos
24
+ from sympy.functions.elementary.hyperbolic import (
25
+ sech, csch, coth, tanh, cosh, asech, acsch, atanh, acoth)
26
+ from sympy.utilities.misc import filldedent
27
+
28
+
29
+ def singularities(expression, symbol, domain=None):
30
+ """
31
+ Find singularities of a given function.
32
+
33
+ Parameters
34
+ ==========
35
+
36
+ expression : Expr
37
+ The target function in which singularities need to be found.
38
+ symbol : Symbol
39
+ The symbol over the values of which the singularity in
40
+ expression in being searched for.
41
+
42
+ Returns
43
+ =======
44
+
45
+ Set
46
+ A set of values for ``symbol`` for which ``expression`` has a
47
+ singularity. An ``EmptySet`` is returned if ``expression`` has no
48
+ singularities for any given value of ``Symbol``.
49
+
50
+ Raises
51
+ ======
52
+
53
+ NotImplementedError
54
+ Methods for determining the singularities of this function have
55
+ not been developed.
56
+
57
+ Notes
58
+ =====
59
+
60
+ This function does not find non-isolated singularities
61
+ nor does it find branch points of the expression.
62
+
63
+ Currently supported functions are:
64
+ - univariate continuous (real or complex) functions
65
+
66
+ References
67
+ ==========
68
+
69
+ .. [1] https://en.wikipedia.org/wiki/Mathematical_singularity
70
+
71
+ Examples
72
+ ========
73
+
74
+ >>> from sympy import singularities, Symbol, log
75
+ >>> x = Symbol('x', real=True)
76
+ >>> y = Symbol('y', real=False)
77
+ >>> singularities(x**2 + x + 1, x)
78
+ EmptySet
79
+ >>> singularities(1/(x + 1), x)
80
+ {-1}
81
+ >>> singularities(1/(y**2 + 1), y)
82
+ {-I, I}
83
+ >>> singularities(1/(y**3 + 1), y)
84
+ {-1, 1/2 - sqrt(3)*I/2, 1/2 + sqrt(3)*I/2}
85
+ >>> singularities(log(x), x)
86
+ {0}
87
+
88
+ """
89
+ from sympy.solvers.solveset import solveset
90
+
91
+ if domain is None:
92
+ domain = S.Reals if symbol.is_real else S.Complexes
93
+ try:
94
+ sings = S.EmptySet
95
+ e = expression.rewrite([sec, csc, cot, tan], cos)
96
+ e = e.rewrite([sech, csch, coth, tanh], cosh)
97
+ for i in e.atoms(Pow):
98
+ if i.exp.is_infinite:
99
+ raise NotImplementedError
100
+ if i.exp.is_negative:
101
+ # XXX: exponent of varying sign not handled
102
+ sings += solveset(i.base, symbol, domain)
103
+ for i in expression.atoms(log, asech, acsch):
104
+ sings += solveset(i.args[0], symbol, domain)
105
+ for i in expression.atoms(atanh, acoth):
106
+ sings += solveset(i.args[0] - 1, symbol, domain)
107
+ sings += solveset(i.args[0] + 1, symbol, domain)
108
+ return sings
109
+ except NotImplementedError:
110
+ raise NotImplementedError(filldedent('''
111
+ Methods for determining the singularities
112
+ of this function have not been developed.'''))
113
+
114
+
115
+ ###########################################################################
116
+ # DIFFERENTIAL CALCULUS METHODS #
117
+ ###########################################################################
118
+
119
+
120
+ def monotonicity_helper(expression, predicate, interval=S.Reals, symbol=None):
121
+ """
122
+ Helper function for functions checking function monotonicity.
123
+
124
+ Parameters
125
+ ==========
126
+
127
+ expression : Expr
128
+ The target function which is being checked
129
+ predicate : function
130
+ The property being tested for. The function takes in an integer
131
+ and returns a boolean. The integer input is the derivative and
132
+ the boolean result should be true if the property is being held,
133
+ and false otherwise.
134
+ interval : Set, optional
135
+ The range of values in which we are testing, defaults to all reals.
136
+ symbol : Symbol, optional
137
+ The symbol present in expression which gets varied over the given range.
138
+
139
+ It returns a boolean indicating whether the interval in which
140
+ the function's derivative satisfies given predicate is a superset
141
+ of the given interval.
142
+
143
+ Returns
144
+ =======
145
+
146
+ Boolean
147
+ True if ``predicate`` is true for all the derivatives when ``symbol``
148
+ is varied in ``range``, False otherwise.
149
+
150
+ """
151
+ from sympy.solvers.solveset import solveset
152
+
153
+ expression = sympify(expression)
154
+ free = expression.free_symbols
155
+
156
+ if symbol is None:
157
+ if len(free) > 1:
158
+ raise NotImplementedError(
159
+ 'The function has not yet been implemented'
160
+ ' for all multivariate expressions.'
161
+ )
162
+
163
+ variable = symbol or (free.pop() if free else Symbol('x'))
164
+ derivative = expression.diff(variable)
165
+ predicate_interval = solveset(predicate(derivative), variable, S.Reals)
166
+ return interval.is_subset(predicate_interval)
167
+
168
+
169
+ def is_increasing(expression, interval=S.Reals, symbol=None):
170
+ """
171
+ Return whether the function is increasing in the given interval.
172
+
173
+ Parameters
174
+ ==========
175
+
176
+ expression : Expr
177
+ The target function which is being checked.
178
+ interval : Set, optional
179
+ The range of values in which we are testing (defaults to set of
180
+ all real numbers).
181
+ symbol : Symbol, optional
182
+ The symbol present in expression which gets varied over the given range.
183
+
184
+ Returns
185
+ =======
186
+
187
+ Boolean
188
+ True if ``expression`` is increasing (either strictly increasing or
189
+ constant) in the given ``interval``, False otherwise.
190
+
191
+ Examples
192
+ ========
193
+
194
+ >>> from sympy import is_increasing
195
+ >>> from sympy.abc import x, y
196
+ >>> from sympy import S, Interval, oo
197
+ >>> is_increasing(x**3 - 3*x**2 + 4*x, S.Reals)
198
+ True
199
+ >>> is_increasing(-x**2, Interval(-oo, 0))
200
+ True
201
+ >>> is_increasing(-x**2, Interval(0, oo))
202
+ False
203
+ >>> is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3))
204
+ False
205
+ >>> is_increasing(x**2 + y, Interval(1, 2), x)
206
+ True
207
+
208
+ """
209
+ return monotonicity_helper(expression, lambda x: x >= 0, interval, symbol)
210
+
211
+
212
+ def is_strictly_increasing(expression, interval=S.Reals, symbol=None):
213
+ """
214
+ Return whether the function is strictly increasing in the given interval.
215
+
216
+ Parameters
217
+ ==========
218
+
219
+ expression : Expr
220
+ The target function which is being checked.
221
+ interval : Set, optional
222
+ The range of values in which we are testing (defaults to set of
223
+ all real numbers).
224
+ symbol : Symbol, optional
225
+ The symbol present in expression which gets varied over the given range.
226
+
227
+ Returns
228
+ =======
229
+
230
+ Boolean
231
+ True if ``expression`` is strictly increasing in the given ``interval``,
232
+ False otherwise.
233
+
234
+ Examples
235
+ ========
236
+
237
+ >>> from sympy import is_strictly_increasing
238
+ >>> from sympy.abc import x, y
239
+ >>> from sympy import Interval, oo
240
+ >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Ropen(-oo, -2))
241
+ True
242
+ >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.Lopen(3, oo))
243
+ True
244
+ >>> is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3))
245
+ False
246
+ >>> is_strictly_increasing(-x**2, Interval(0, oo))
247
+ False
248
+ >>> is_strictly_increasing(-x**2 + y, Interval(-oo, 0), x)
249
+ False
250
+
251
+ """
252
+ return monotonicity_helper(expression, lambda x: x > 0, interval, symbol)
253
+
254
+
255
+ def is_decreasing(expression, interval=S.Reals, symbol=None):
256
+ """
257
+ Return whether the function is decreasing in the given interval.
258
+
259
+ Parameters
260
+ ==========
261
+
262
+ expression : Expr
263
+ The target function which is being checked.
264
+ interval : Set, optional
265
+ The range of values in which we are testing (defaults to set of
266
+ all real numbers).
267
+ symbol : Symbol, optional
268
+ The symbol present in expression which gets varied over the given range.
269
+
270
+ Returns
271
+ =======
272
+
273
+ Boolean
274
+ True if ``expression`` is decreasing (either strictly decreasing or
275
+ constant) in the given ``interval``, False otherwise.
276
+
277
+ Examples
278
+ ========
279
+
280
+ >>> from sympy import is_decreasing
281
+ >>> from sympy.abc import x, y
282
+ >>> from sympy import S, Interval, oo
283
+ >>> is_decreasing(1/(x**2 - 3*x), Interval.open(S(3)/2, 3))
284
+ True
285
+ >>> is_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3))
286
+ True
287
+ >>> is_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
288
+ True
289
+ >>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2))
290
+ False
291
+ >>> is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5))
292
+ False
293
+ >>> is_decreasing(-x**2, Interval(-oo, 0))
294
+ False
295
+ >>> is_decreasing(-x**2 + y, Interval(-oo, 0), x)
296
+ False
297
+
298
+ """
299
+ return monotonicity_helper(expression, lambda x: x <= 0, interval, symbol)
300
+
301
+
302
+ def is_strictly_decreasing(expression, interval=S.Reals, symbol=None):
303
+ """
304
+ Return whether the function is strictly decreasing in the given interval.
305
+
306
+ Parameters
307
+ ==========
308
+
309
+ expression : Expr
310
+ The target function which is being checked.
311
+ interval : Set, optional
312
+ The range of values in which we are testing (defaults to set of
313
+ all real numbers).
314
+ symbol : Symbol, optional
315
+ The symbol present in expression which gets varied over the given range.
316
+
317
+ Returns
318
+ =======
319
+
320
+ Boolean
321
+ True if ``expression`` is strictly decreasing in the given ``interval``,
322
+ False otherwise.
323
+
324
+ Examples
325
+ ========
326
+
327
+ >>> from sympy import is_strictly_decreasing
328
+ >>> from sympy.abc import x, y
329
+ >>> from sympy import S, Interval, oo
330
+ >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
331
+ True
332
+ >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, S(3)/2))
333
+ False
334
+ >>> is_strictly_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, 1.5))
335
+ False
336
+ >>> is_strictly_decreasing(-x**2, Interval(-oo, 0))
337
+ False
338
+ >>> is_strictly_decreasing(-x**2 + y, Interval(-oo, 0), x)
339
+ False
340
+
341
+ """
342
+ return monotonicity_helper(expression, lambda x: x < 0, interval, symbol)
343
+
344
+
345
+ def is_monotonic(expression, interval=S.Reals, symbol=None):
346
+ """
347
+ Return whether the function is monotonic in the given interval.
348
+
349
+ Parameters
350
+ ==========
351
+
352
+ expression : Expr
353
+ The target function which is being checked.
354
+ interval : Set, optional
355
+ The range of values in which we are testing (defaults to set of
356
+ all real numbers).
357
+ symbol : Symbol, optional
358
+ The symbol present in expression which gets varied over the given range.
359
+
360
+ Returns
361
+ =======
362
+
363
+ Boolean
364
+ True if ``expression`` is monotonic in the given ``interval``,
365
+ False otherwise.
366
+
367
+ Raises
368
+ ======
369
+
370
+ NotImplementedError
371
+ Monotonicity check has not been implemented for the queried function.
372
+
373
+ Examples
374
+ ========
375
+
376
+ >>> from sympy import is_monotonic
377
+ >>> from sympy.abc import x, y
378
+ >>> from sympy import S, Interval, oo
379
+ >>> is_monotonic(1/(x**2 - 3*x), Interval.open(S(3)/2, 3))
380
+ True
381
+ >>> is_monotonic(1/(x**2 - 3*x), Interval.open(1.5, 3))
382
+ True
383
+ >>> is_monotonic(1/(x**2 - 3*x), Interval.Lopen(3, oo))
384
+ True
385
+ >>> is_monotonic(x**3 - 3*x**2 + 4*x, S.Reals)
386
+ True
387
+ >>> is_monotonic(-x**2, S.Reals)
388
+ False
389
+ >>> is_monotonic(x**2 + y + 1, Interval(1, 2), x)
390
+ True
391
+
392
+ """
393
+ from sympy.solvers.solveset import solveset
394
+
395
+ expression = sympify(expression)
396
+
397
+ free = expression.free_symbols
398
+ if symbol is None and len(free) > 1:
399
+ raise NotImplementedError(
400
+ 'is_monotonic has not yet been implemented'
401
+ ' for all multivariate expressions.'
402
+ )
403
+
404
+ variable = symbol or (free.pop() if free else Symbol('x'))
405
+ turning_points = solveset(expression.diff(variable), variable, interval)
406
+ return interval.intersection(turning_points) is S.EmptySet
lib/python3.12/site-packages/sympy/calculus/tests/__init__.py ADDED
File without changes
lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (199 Bytes). View file
 
lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_accumulationbounds.cpython-312.pyc ADDED
Binary file (32.8 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_euler.cpython-312.pyc ADDED
Binary file (7.33 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_finite_diff.cpython-312.pyc ADDED
Binary file (16.7 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_singularities.cpython-312.pyc ADDED
Binary file (11.8 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_util.cpython-312.pyc ADDED
Binary file (49.9 kB). View file
 
lib/python3.12/site-packages/sympy/calculus/tests/test_accumulationbounds.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.numbers import (E, Rational, oo, pi, zoo)
2
+ from sympy.core.singleton import S
3
+ from sympy.core.symbol import Symbol
4
+ from sympy.functions.elementary.exponential import (exp, log)
5
+ from sympy.functions.elementary.miscellaneous import (Max, Min, sqrt)
6
+ from sympy.functions.elementary.trigonometric import (cos, sin, tan)
7
+ from sympy.calculus.accumulationbounds import AccumBounds
8
+ from sympy.core import Add, Mul, Pow
9
+ from sympy.core.expr import unchanged
10
+ from sympy.testing.pytest import raises, XFAIL
11
+ from sympy.abc import x
12
+
13
+ a = Symbol('a', real=True)
14
+ B = AccumBounds
15
+
16
+
17
+ def test_AccumBounds():
18
+ assert B(1, 2).args == (1, 2)
19
+ assert B(1, 2).delta is S.One
20
+ assert B(1, 2).mid == Rational(3, 2)
21
+ assert B(1, 3).is_real == True
22
+
23
+ assert B(1, 1) is S.One
24
+
25
+ assert B(1, 2) + 1 == B(2, 3)
26
+ assert 1 + B(1, 2) == B(2, 3)
27
+ assert B(1, 2) + B(2, 3) == B(3, 5)
28
+
29
+ assert -B(1, 2) == B(-2, -1)
30
+
31
+ assert B(1, 2) - 1 == B(0, 1)
32
+ assert 1 - B(1, 2) == B(-1, 0)
33
+ assert B(2, 3) - B(1, 2) == B(0, 2)
34
+
35
+ assert x + B(1, 2) == Add(B(1, 2), x)
36
+ assert a + B(1, 2) == B(1 + a, 2 + a)
37
+ assert B(1, 2) - x == Add(B(1, 2), -x)
38
+
39
+ assert B(-oo, 1) + oo == B(-oo, oo)
40
+ assert B(1, oo) + oo is oo
41
+ assert B(1, oo) - oo == B(-oo, oo)
42
+ assert (-oo - B(-1, oo)) is -oo
43
+ assert B(-oo, 1) - oo is -oo
44
+
45
+ assert B(1, oo) - oo == B(-oo, oo)
46
+ assert B(-oo, 1) - (-oo) == B(-oo, oo)
47
+ assert (oo - B(1, oo)) == B(-oo, oo)
48
+ assert (-oo - B(1, oo)) is -oo
49
+
50
+ assert B(1, 2)/2 == B(S.Half, 1)
51
+ assert 2/B(2, 3) == B(Rational(2, 3), 1)
52
+ assert 1/B(-1, 1) == B(-oo, oo)
53
+
54
+ assert abs(B(1, 2)) == B(1, 2)
55
+ assert abs(B(-2, -1)) == B(1, 2)
56
+ assert abs(B(-2, 1)) == B(0, 2)
57
+ assert abs(B(-1, 2)) == B(0, 2)
58
+ c = Symbol('c')
59
+ raises(ValueError, lambda: B(0, c))
60
+ raises(ValueError, lambda: B(1, -1))
61
+ r = Symbol('r', real=True)
62
+ raises(ValueError, lambda: B(r, r - 1))
63
+
64
+
65
+ def test_AccumBounds_mul():
66
+ assert B(1, 2)*2 == B(2, 4)
67
+ assert 2*B(1, 2) == B(2, 4)
68
+ assert B(1, 2)*B(2, 3) == B(2, 6)
69
+ assert B(0, 2)*B(2, oo) == B(0, oo)
70
+ l, r = B(-oo, oo), B(-a, a)
71
+ assert l*r == B(-oo, oo)
72
+ assert r*l == B(-oo, oo)
73
+ l, r = B(1, oo), B(-3, -2)
74
+ assert l*r == B(-oo, -2)
75
+ assert r*l == B(-oo, -2)
76
+ assert B(1, 2)*0 == 0
77
+ assert B(1, oo)*0 == B(0, oo)
78
+ assert B(-oo, 1)*0 == B(-oo, 0)
79
+ assert B(-oo, oo)*0 == B(-oo, oo)
80
+
81
+ assert B(1, 2)*x == Mul(B(1, 2), x, evaluate=False)
82
+
83
+ assert B(0, 2)*oo == B(0, oo)
84
+ assert B(-2, 0)*oo == B(-oo, 0)
85
+ assert B(0, 2)*(-oo) == B(-oo, 0)
86
+ assert B(-2, 0)*(-oo) == B(0, oo)
87
+ assert B(-1, 1)*oo == B(-oo, oo)
88
+ assert B(-1, 1)*(-oo) == B(-oo, oo)
89
+ assert B(-oo, oo)*oo == B(-oo, oo)
90
+
91
+
92
+ def test_AccumBounds_div():
93
+ assert B(-1, 3)/B(3, 4) == B(Rational(-1, 3), 1)
94
+ assert B(-2, 4)/B(-3, 4) == B(-oo, oo)
95
+ assert B(-3, -2)/B(-4, 0) == B(S.Half, oo)
96
+
97
+ # these two tests can have a better answer
98
+ # after Union of B is improved
99
+ assert B(-3, -2)/B(-2, 1) == B(-oo, oo)
100
+ assert B(2, 3)/B(-2, 2) == B(-oo, oo)
101
+
102
+ assert B(-3, -2)/B(0, 4) == B(-oo, Rational(-1, 2))
103
+ assert B(2, 4)/B(-3, 0) == B(-oo, Rational(-2, 3))
104
+ assert B(2, 4)/B(0, 3) == B(Rational(2, 3), oo)
105
+
106
+ assert B(0, 1)/B(0, 1) == B(0, oo)
107
+ assert B(-1, 0)/B(0, 1) == B(-oo, 0)
108
+ assert B(-1, 2)/B(-2, 2) == B(-oo, oo)
109
+
110
+ assert 1/B(-1, 2) == B(-oo, oo)
111
+ assert 1/B(0, 2) == B(S.Half, oo)
112
+ assert (-1)/B(0, 2) == B(-oo, Rational(-1, 2))
113
+ assert 1/B(-oo, 0) == B(-oo, 0)
114
+ assert 1/B(-1, 0) == B(-oo, -1)
115
+ assert (-2)/B(-oo, 0) == B(0, oo)
116
+ assert 1/B(-oo, -1) == B(-1, 0)
117
+
118
+ assert B(1, 2)/a == Mul(B(1, 2), 1/a, evaluate=False)
119
+
120
+ assert B(1, 2)/0 == B(1, 2)*zoo
121
+ assert B(1, oo)/oo == B(0, oo)
122
+ assert B(1, oo)/(-oo) == B(-oo, 0)
123
+ assert B(-oo, -1)/oo == B(-oo, 0)
124
+ assert B(-oo, -1)/(-oo) == B(0, oo)
125
+ assert B(-oo, oo)/oo == B(-oo, oo)
126
+ assert B(-oo, oo)/(-oo) == B(-oo, oo)
127
+ assert B(-1, oo)/oo == B(0, oo)
128
+ assert B(-1, oo)/(-oo) == B(-oo, 0)
129
+ assert B(-oo, 1)/oo == B(-oo, 0)
130
+ assert B(-oo, 1)/(-oo) == B(0, oo)
131
+
132
+
133
+ def test_issue_18795():
134
+ r = Symbol('r', real=True)
135
+ a = B(-1,1)
136
+ c = B(7, oo)
137
+ b = B(-oo, oo)
138
+ assert c - tan(r) == B(7-tan(r), oo)
139
+ assert b + tan(r) == B(-oo, oo)
140
+ assert (a + r)/a == B(-oo, oo)*B(r - 1, r + 1)
141
+ assert (b + a)/a == B(-oo, oo)
142
+
143
+
144
+ def test_AccumBounds_func():
145
+ assert (x**2 + 2*x + 1).subs(x, B(-1, 1)) == B(-1, 4)
146
+ assert exp(B(0, 1)) == B(1, E)
147
+ assert exp(B(-oo, oo)) == B(0, oo)
148
+ assert log(B(3, 6)) == B(log(3), log(6))
149
+
150
+
151
+ @XFAIL
152
+ def test_AccumBounds_powf():
153
+ nn = Symbol('nn', nonnegative=True)
154
+ assert B(1 + nn, 2 + nn)**B(1, 2) == B(1 + nn, (2 + nn)**2)
155
+ i = Symbol('i', integer=True, negative=True)
156
+ assert B(1, 2)**i == B(2**i, 1)
157
+
158
+
159
+ def test_AccumBounds_pow():
160
+ assert B(0, 2)**2 == B(0, 4)
161
+ assert B(-1, 1)**2 == B(0, 1)
162
+ assert B(1, 2)**2 == B(1, 4)
163
+ assert B(-1, 2)**3 == B(-1, 8)
164
+ assert B(-1, 1)**0 == 1
165
+
166
+ assert B(1, 2)**Rational(5, 2) == B(1, 4*sqrt(2))
167
+ assert B(0, 2)**S.Half == B(0, sqrt(2))
168
+
169
+ neg = Symbol('neg', negative=True)
170
+ assert unchanged(Pow, B(neg, 1), S.Half)
171
+ nn = Symbol('nn', nonnegative=True)
172
+ assert B(nn, nn + 1)**S.Half == B(sqrt(nn), sqrt(nn + 1))
173
+ assert B(nn, nn + 1)**nn == B(nn**nn, (nn + 1)**nn)
174
+ assert unchanged(Pow, B(nn, nn + 1), x)
175
+ i = Symbol('i', integer=True)
176
+ assert B(1, 2)**i == B(Min(1, 2**i), Max(1, 2**i))
177
+ i = Symbol('i', integer=True, nonnegative=True)
178
+ assert B(1, 2)**i == B(1, 2**i)
179
+ assert B(0, 1)**i == B(0**i, 1)
180
+
181
+ assert B(1, 5)**(-2) == B(Rational(1, 25), 1)
182
+ assert B(-1, 3)**(-2) == B(0, oo)
183
+ assert B(0, 2)**(-3) == B(Rational(1, 8), oo)
184
+ assert B(-2, 0)**(-3) == B(-oo, -Rational(1, 8))
185
+ assert B(0, 2)**(-2) == B(Rational(1, 4), oo)
186
+ assert B(-1, 2)**(-3) == B(-oo, oo)
187
+ assert B(-3, -2)**(-3) == B(Rational(-1, 8), Rational(-1, 27))
188
+ assert B(-3, -2)**(-2) == B(Rational(1, 9), Rational(1, 4))
189
+ assert B(0, oo)**S.Half == B(0, oo)
190
+ assert B(-oo, 0)**(-2) == B(0, oo)
191
+ assert B(-2, 0)**(-2) == B(Rational(1, 4), oo)
192
+
193
+ assert B(Rational(1, 3), S.Half)**oo is S.Zero
194
+ assert B(0, S.Half)**oo is S.Zero
195
+ assert B(S.Half, 1)**oo == B(0, oo)
196
+ assert B(0, 1)**oo == B(0, oo)
197
+ assert B(2, 3)**oo is oo
198
+ assert B(1, 2)**oo == B(0, oo)
199
+ assert B(S.Half, 3)**oo == B(0, oo)
200
+ assert B(Rational(-1, 3), Rational(-1, 4))**oo is S.Zero
201
+ assert B(-1, Rational(-1, 2))**oo is S.NaN
202
+ assert B(-3, -2)**oo is zoo
203
+ assert B(-2, -1)**oo is S.NaN
204
+ assert B(-2, Rational(-1, 2))**oo is S.NaN
205
+ assert B(Rational(-1, 2), S.Half)**oo is S.Zero
206
+ assert B(Rational(-1, 2), 1)**oo == B(0, oo)
207
+ assert B(Rational(-2, 3), 2)**oo == B(0, oo)
208
+ assert B(-1, 1)**oo == B(-oo, oo)
209
+ assert B(-1, S.Half)**oo == B(-oo, oo)
210
+ assert B(-1, 2)**oo == B(-oo, oo)
211
+ assert B(-2, S.Half)**oo == B(-oo, oo)
212
+
213
+ assert B(1, 2)**x == Pow(B(1, 2), x, evaluate=False)
214
+
215
+ assert B(2, 3)**(-oo) is S.Zero
216
+ assert B(0, 2)**(-oo) == B(0, oo)
217
+ assert B(-1, 2)**(-oo) == B(-oo, oo)
218
+
219
+ assert (tan(x)**sin(2*x)).subs(x, B(0, pi/2)) == \
220
+ Pow(B(-oo, oo), B(0, 1))
221
+
222
+
223
+ def test_AccumBounds_exponent():
224
+ # base is 0
225
+ z = 0**B(a, a + S.Half)
226
+ assert z.subs(a, 0) == B(0, 1)
227
+ assert z.subs(a, 1) == 0
228
+ p = z.subs(a, -1)
229
+ assert p.is_Pow and p.args == (0, B(-1, -S.Half))
230
+ # base > 0
231
+ # when base is 1 the type of bounds does not matter
232
+ assert 1**B(a, a + 1) == 1
233
+ # otherwise we need to know if 0 is in the bounds
234
+ assert S.Half**B(-2, 2) == B(S(1)/4, 4)
235
+ assert 2**B(-2, 2) == B(S(1)/4, 4)
236
+
237
+ # +eps may introduce +oo
238
+ # if there is a negative integer exponent
239
+ assert B(0, 1)**B(S(1)/2, 1) == B(0, 1)
240
+ assert B(0, 1)**B(0, 1) == B(0, 1)
241
+
242
+ # positive bases have positive bounds
243
+ assert B(2, 3)**B(-3, -2) == B(S(1)/27, S(1)/4)
244
+ assert B(2, 3)**B(-3, 2) == B(S(1)/27, 9)
245
+
246
+ # bounds generating imaginary parts unevaluated
247
+ assert unchanged(Pow, B(-1, 1), B(1, 2))
248
+ assert B(0, S(1)/2)**B(1, oo) == B(0, S(1)/2)
249
+ assert B(0, 1)**B(1, oo) == B(0, oo)
250
+ assert B(0, 2)**B(1, oo) == B(0, oo)
251
+ assert B(0, oo)**B(1, oo) == B(0, oo)
252
+ assert B(S(1)/2, 1)**B(1, oo) == B(0, oo)
253
+ assert B(S(1)/2, 1)**B(-oo, -1) == B(0, oo)
254
+ assert B(S(1)/2, 1)**B(-oo, oo) == B(0, oo)
255
+ assert B(S(1)/2, 2)**B(1, oo) == B(0, oo)
256
+ assert B(S(1)/2, 2)**B(-oo, -1) == B(0, oo)
257
+ assert B(S(1)/2, 2)**B(-oo, oo) == B(0, oo)
258
+ assert B(S(1)/2, oo)**B(1, oo) == B(0, oo)
259
+ assert B(S(1)/2, oo)**B(-oo, -1) == B(0, oo)
260
+ assert B(S(1)/2, oo)**B(-oo, oo) == B(0, oo)
261
+ assert B(1, 2)**B(1, oo) == B(0, oo)
262
+ assert B(1, 2)**B(-oo, -1) == B(0, oo)
263
+ assert B(1, 2)**B(-oo, oo) == B(0, oo)
264
+ assert B(1, oo)**B(1, oo) == B(0, oo)
265
+ assert B(1, oo)**B(-oo, -1) == B(0, oo)
266
+ assert B(1, oo)**B(-oo, oo) == B(0, oo)
267
+ assert B(2, oo)**B(1, oo) == B(2, oo)
268
+ assert B(2, oo)**B(-oo, -1) == B(0, S(1)/2)
269
+ assert B(2, oo)**B(-oo, oo) == B(0, oo)
270
+
271
+
272
+ def test_comparison_AccumBounds():
273
+ assert (B(1, 3) < 4) == S.true
274
+ assert (B(1, 3) < -1) == S.false
275
+ assert (B(1, 3) < 2).rel_op == '<'
276
+ assert (B(1, 3) <= 2).rel_op == '<='
277
+
278
+ assert (B(1, 3) > 4) == S.false
279
+ assert (B(1, 3) > -1) == S.true
280
+ assert (B(1, 3) > 2).rel_op == '>'
281
+ assert (B(1, 3) >= 2).rel_op == '>='
282
+
283
+ assert (B(1, 3) < B(4, 6)) == S.true
284
+ assert (B(1, 3) < B(2, 4)).rel_op == '<'
285
+ assert (B(1, 3) < B(-2, 0)) == S.false
286
+
287
+ assert (B(1, 3) <= B(4, 6)) == S.true
288
+ assert (B(1, 3) <= B(-2, 0)) == S.false
289
+
290
+ assert (B(1, 3) > B(4, 6)) == S.false
291
+ assert (B(1, 3) > B(-2, 0)) == S.true
292
+
293
+ assert (B(1, 3) >= B(4, 6)) == S.false
294
+ assert (B(1, 3) >= B(-2, 0)) == S.true
295
+
296
+ # issue 13499
297
+ assert (cos(x) > 0).subs(x, oo) == (B(-1, 1) > 0)
298
+
299
+ c = Symbol('c')
300
+ raises(TypeError, lambda: (B(0, 1) < c))
301
+ raises(TypeError, lambda: (B(0, 1) <= c))
302
+ raises(TypeError, lambda: (B(0, 1) > c))
303
+ raises(TypeError, lambda: (B(0, 1) >= c))
304
+
305
+
306
+ def test_contains_AccumBounds():
307
+ assert (1 in B(1, 2)) == S.true
308
+ raises(TypeError, lambda: a in B(1, 2))
309
+ assert 0 in B(-1, 0)
310
+ raises(TypeError, lambda:
311
+ (cos(1)**2 + sin(1)**2 - 1) in B(-1, 0))
312
+ assert (-oo in B(1, oo)) == S.true
313
+ assert (oo in B(-oo, 0)) == S.true
314
+
315
+ # issue 13159
316
+ assert Mul(0, B(-1, 1)) == Mul(B(-1, 1), 0) == 0
317
+ import itertools
318
+ for perm in itertools.permutations([0, B(-1, 1), x]):
319
+ assert Mul(*perm) == 0
320
+
321
+
322
+ def test_intersection_AccumBounds():
323
+ assert B(0, 3).intersection(B(1, 2)) == B(1, 2)
324
+ assert B(0, 3).intersection(B(1, 4)) == B(1, 3)
325
+ assert B(0, 3).intersection(B(-1, 2)) == B(0, 2)
326
+ assert B(0, 3).intersection(B(-1, 4)) == B(0, 3)
327
+ assert B(0, 1).intersection(B(2, 3)) == S.EmptySet
328
+ raises(TypeError, lambda: B(0, 3).intersection(1))
329
+
330
+
331
+ def test_union_AccumBounds():
332
+ assert B(0, 3).union(B(1, 2)) == B(0, 3)
333
+ assert B(0, 3).union(B(1, 4)) == B(0, 4)
334
+ assert B(0, 3).union(B(-1, 2)) == B(-1, 3)
335
+ assert B(0, 3).union(B(-1, 4)) == B(-1, 4)
336
+ raises(TypeError, lambda: B(0, 3).union(1))
lib/python3.12/site-packages/sympy/calculus/tests/test_euler.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.function import (Derivative as D, Function)
2
+ from sympy.core.relational import Eq
3
+ from sympy.core.symbol import (Symbol, symbols)
4
+ from sympy.functions.elementary.trigonometric import (cos, sin)
5
+ from sympy.testing.pytest import raises
6
+ from sympy.calculus.euler import euler_equations as euler
7
+
8
+
9
+ def test_euler_interface():
10
+ x = Function('x')
11
+ y = Symbol('y')
12
+ t = Symbol('t')
13
+ raises(TypeError, lambda: euler())
14
+ raises(TypeError, lambda: euler(D(x(t), t)*y(t), [x(t), y]))
15
+ raises(ValueError, lambda: euler(D(x(t), t)*x(y), [x(t), x(y)]))
16
+ raises(TypeError, lambda: euler(D(x(t), t)**2, x(0)))
17
+ raises(TypeError, lambda: euler(D(x(t), t)*y(t), [t]))
18
+ assert euler(D(x(t), t)**2/2, {x(t)}) == [Eq(-D(x(t), t, t), 0)]
19
+ assert euler(D(x(t), t)**2/2, x(t), {t}) == [Eq(-D(x(t), t, t), 0)]
20
+
21
+
22
+ def test_euler_pendulum():
23
+ x = Function('x')
24
+ t = Symbol('t')
25
+ L = D(x(t), t)**2/2 + cos(x(t))
26
+ assert euler(L, x(t), t) == [Eq(-sin(x(t)) - D(x(t), t, t), 0)]
27
+
28
+
29
+ def test_euler_henonheiles():
30
+ x = Function('x')
31
+ y = Function('y')
32
+ t = Symbol('t')
33
+ L = sum(D(z(t), t)**2/2 - z(t)**2/2 for z in [x, y])
34
+ L += -x(t)**2*y(t) + y(t)**3/3
35
+ assert euler(L, [x(t), y(t)], t) == [Eq(-2*x(t)*y(t) - x(t) -
36
+ D(x(t), t, t), 0),
37
+ Eq(-x(t)**2 + y(t)**2 -
38
+ y(t) - D(y(t), t, t), 0)]
39
+
40
+
41
+ def test_euler_sineg():
42
+ psi = Function('psi')
43
+ t = Symbol('t')
44
+ x = Symbol('x')
45
+ L = D(psi(t, x), t)**2/2 - D(psi(t, x), x)**2/2 + cos(psi(t, x))
46
+ assert euler(L, psi(t, x), [t, x]) == [Eq(-sin(psi(t, x)) -
47
+ D(psi(t, x), t, t) +
48
+ D(psi(t, x), x, x), 0)]
49
+
50
+
51
+ def test_euler_high_order():
52
+ # an example from hep-th/0309038
53
+ m = Symbol('m')
54
+ k = Symbol('k')
55
+ x = Function('x')
56
+ y = Function('y')
57
+ t = Symbol('t')
58
+ L = (m*D(x(t), t)**2/2 + m*D(y(t), t)**2/2 -
59
+ k*D(x(t), t)*D(y(t), t, t) + k*D(y(t), t)*D(x(t), t, t))
60
+ assert euler(L, [x(t), y(t)]) == [Eq(2*k*D(y(t), t, t, t) -
61
+ m*D(x(t), t, t), 0),
62
+ Eq(-2*k*D(x(t), t, t, t) -
63
+ m*D(y(t), t, t), 0)]
64
+
65
+ w = Symbol('w')
66
+ L = D(x(t, w), t, w)**2/2
67
+ assert euler(L) == [Eq(D(x(t, w), t, t, w, w), 0)]
68
+
69
+ def test_issue_18653():
70
+ x, y, z = symbols("x y z")
71
+ f, g, h = symbols("f g h", cls=Function, args=(x, y))
72
+ f, g, h = f(), g(), h()
73
+ expr2 = f.diff(x)*h.diff(z)
74
+ assert euler(expr2, (f,), (x, y)) == []
lib/python3.12/site-packages/sympy/calculus/tests/test_finite_diff.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from itertools import product
2
+
3
+ from sympy.core.function import (Function, diff)
4
+ from sympy.core.numbers import Rational
5
+ from sympy.core.singleton import S
6
+ from sympy.core.symbol import symbols
7
+ from sympy.functions.elementary.exponential import exp
8
+ from sympy.calculus.finite_diff import (
9
+ apply_finite_diff, differentiate_finite, finite_diff_weights,
10
+ _as_finite_diff
11
+ )
12
+ from sympy.testing.pytest import raises, warns_deprecated_sympy
13
+
14
+
15
+ def test_apply_finite_diff():
16
+ x, h = symbols('x h')
17
+ f = Function('f')
18
+ assert (apply_finite_diff(1, [x-h, x+h], [f(x-h), f(x+h)], x) -
19
+ (f(x+h)-f(x-h))/(2*h)).simplify() == 0
20
+
21
+ assert (apply_finite_diff(1, [5, 6, 7], [f(5), f(6), f(7)], 5) -
22
+ (Rational(-3, 2)*f(5) + 2*f(6) - S.Half*f(7))).simplify() == 0
23
+ raises(ValueError, lambda: apply_finite_diff(1, [x, h], [f(x)]))
24
+
25
+
26
+ def test_finite_diff_weights():
27
+
28
+ d = finite_diff_weights(1, [5, 6, 7], 5)
29
+ assert d[1][2] == [Rational(-3, 2), 2, Rational(-1, 2)]
30
+
31
+ # Table 1, p. 702 in doi:10.1090/S0025-5718-1988-0935077-0
32
+ # --------------------------------------------------------
33
+ xl = [0, 1, -1, 2, -2, 3, -3, 4, -4]
34
+
35
+ # d holds all coefficients
36
+ d = finite_diff_weights(4, xl, S.Zero)
37
+
38
+ # Zeroeth derivative
39
+ for i in range(5):
40
+ assert d[0][i] == [S.One] + [S.Zero]*8
41
+
42
+ # First derivative
43
+ assert d[1][0] == [S.Zero]*9
44
+ assert d[1][2] == [S.Zero, S.Half, Rational(-1, 2)] + [S.Zero]*6
45
+ assert d[1][4] == [S.Zero, Rational(2, 3), Rational(-2, 3), Rational(-1, 12), Rational(1, 12)] + [S.Zero]*4
46
+ assert d[1][6] == [S.Zero, Rational(3, 4), Rational(-3, 4), Rational(-3, 20), Rational(3, 20),
47
+ Rational(1, 60), Rational(-1, 60)] + [S.Zero]*2
48
+ assert d[1][8] == [S.Zero, Rational(4, 5), Rational(-4, 5), Rational(-1, 5), Rational(1, 5),
49
+ Rational(4, 105), Rational(-4, 105), Rational(-1, 280), Rational(1, 280)]
50
+
51
+ # Second derivative
52
+ for i in range(2):
53
+ assert d[2][i] == [S.Zero]*9
54
+ assert d[2][2] == [-S(2), S.One, S.One] + [S.Zero]*6
55
+ assert d[2][4] == [Rational(-5, 2), Rational(4, 3), Rational(4, 3), Rational(-1, 12), Rational(-1, 12)] + [S.Zero]*4
56
+ assert d[2][6] == [Rational(-49, 18), Rational(3, 2), Rational(3, 2), Rational(-3, 20), Rational(-3, 20),
57
+ Rational(1, 90), Rational(1, 90)] + [S.Zero]*2
58
+ assert d[2][8] == [Rational(-205, 72), Rational(8, 5), Rational(8, 5), Rational(-1, 5), Rational(-1, 5),
59
+ Rational(8, 315), Rational(8, 315), Rational(-1, 560), Rational(-1, 560)]
60
+
61
+ # Third derivative
62
+ for i in range(3):
63
+ assert d[3][i] == [S.Zero]*9
64
+ assert d[3][4] == [S.Zero, -S.One, S.One, S.Half, Rational(-1, 2)] + [S.Zero]*4
65
+ assert d[3][6] == [S.Zero, Rational(-13, 8), Rational(13, 8), S.One, -S.One,
66
+ Rational(-1, 8), Rational(1, 8)] + [S.Zero]*2
67
+ assert d[3][8] == [S.Zero, Rational(-61, 30), Rational(61, 30), Rational(169, 120), Rational(-169, 120),
68
+ Rational(-3, 10), Rational(3, 10), Rational(7, 240), Rational(-7, 240)]
69
+
70
+ # Fourth derivative
71
+ for i in range(4):
72
+ assert d[4][i] == [S.Zero]*9
73
+ assert d[4][4] == [S(6), -S(4), -S(4), S.One, S.One] + [S.Zero]*4
74
+ assert d[4][6] == [Rational(28, 3), Rational(-13, 2), Rational(-13, 2), S(2), S(2),
75
+ Rational(-1, 6), Rational(-1, 6)] + [S.Zero]*2
76
+ assert d[4][8] == [Rational(91, 8), Rational(-122, 15), Rational(-122, 15), Rational(169, 60), Rational(169, 60),
77
+ Rational(-2, 5), Rational(-2, 5), Rational(7, 240), Rational(7, 240)]
78
+
79
+ # Table 2, p. 703 in doi:10.1090/S0025-5718-1988-0935077-0
80
+ # --------------------------------------------------------
81
+ xl = [[j/S(2) for j in list(range(-i*2+1, 0, 2))+list(range(1, i*2+1, 2))]
82
+ for i in range(1, 5)]
83
+
84
+ # d holds all coefficients
85
+ d = [finite_diff_weights({0: 1, 1: 2, 2: 4, 3: 4}[i], xl[i], 0) for
86
+ i in range(4)]
87
+
88
+ # Zeroth derivative
89
+ assert d[0][0][1] == [S.Half, S.Half]
90
+ assert d[1][0][3] == [Rational(-1, 16), Rational(9, 16), Rational(9, 16), Rational(-1, 16)]
91
+ assert d[2][0][5] == [Rational(3, 256), Rational(-25, 256), Rational(75, 128), Rational(75, 128),
92
+ Rational(-25, 256), Rational(3, 256)]
93
+ assert d[3][0][7] == [Rational(-5, 2048), Rational(49, 2048), Rational(-245, 2048), Rational(1225, 2048),
94
+ Rational(1225, 2048), Rational(-245, 2048), Rational(49, 2048), Rational(-5, 2048)]
95
+
96
+ # First derivative
97
+ assert d[0][1][1] == [-S.One, S.One]
98
+ assert d[1][1][3] == [Rational(1, 24), Rational(-9, 8), Rational(9, 8), Rational(-1, 24)]
99
+ assert d[2][1][5] == [Rational(-3, 640), Rational(25, 384), Rational(-75, 64),
100
+ Rational(75, 64), Rational(-25, 384), Rational(3, 640)]
101
+ assert d[3][1][7] == [Rational(5, 7168), Rational(-49, 5120),
102
+ Rational(245, 3072), Rational(-1225, 1024),
103
+ Rational(1225, 1024), Rational(-245, 3072),
104
+ Rational(49, 5120), Rational(-5, 7168)]
105
+
106
+ # Reasonably the rest of the table is also correct... (testing of that
107
+ # deemed excessive at the moment)
108
+ raises(ValueError, lambda: finite_diff_weights(-1, [1, 2]))
109
+ raises(ValueError, lambda: finite_diff_weights(1.2, [1, 2]))
110
+ x = symbols('x')
111
+ raises(ValueError, lambda: finite_diff_weights(x, [1, 2]))
112
+
113
+
114
+ def test_as_finite_diff():
115
+ x = symbols('x')
116
+ f = Function('f')
117
+ dx = Function('dx')
118
+
119
+ _as_finite_diff(f(x).diff(x), [x-2, x-1, x, x+1, x+2])
120
+
121
+ # Use of undefined functions in ``points``
122
+ df_true = -f(x+dx(x)/2-dx(x+dx(x)/2)/2) / dx(x+dx(x)/2) \
123
+ + f(x+dx(x)/2+dx(x+dx(x)/2)/2) / dx(x+dx(x)/2)
124
+ df_test = diff(f(x), x).as_finite_difference(points=dx(x), x0=x+dx(x)/2)
125
+ assert (df_test - df_true).simplify() == 0
126
+
127
+
128
+ def test_differentiate_finite():
129
+ x, y, h = symbols('x y h')
130
+ f = Function('f')
131
+ with warns_deprecated_sympy():
132
+ res0 = differentiate_finite(f(x, y) + exp(42), x, y, evaluate=True)
133
+ xm, xp, ym, yp = [v + sign*S.Half for v, sign in product([x, y], [-1, 1])]
134
+ ref0 = f(xm, ym) + f(xp, yp) - f(xm, yp) - f(xp, ym)
135
+ assert (res0 - ref0).simplify() == 0
136
+
137
+ g = Function('g')
138
+ with warns_deprecated_sympy():
139
+ res1 = differentiate_finite(f(x)*g(x) + 42, x, evaluate=True)
140
+ ref1 = (-f(x - S.Half) + f(x + S.Half))*g(x) + \
141
+ (-g(x - S.Half) + g(x + S.Half))*f(x)
142
+ assert (res1 - ref1).simplify() == 0
143
+
144
+ res2 = differentiate_finite(f(x) + x**3 + 42, x, points=[x-1, x+1])
145
+ ref2 = (f(x + 1) + (x + 1)**3 - f(x - 1) - (x - 1)**3)/2
146
+ assert (res2 - ref2).simplify() == 0
147
+ raises(TypeError, lambda: differentiate_finite(f(x)*g(x), x,
148
+ pints=[x-1, x+1]))
149
+
150
+ res3 = differentiate_finite(f(x)*g(x).diff(x), x)
151
+ ref3 = (-g(x) + g(x + 1))*f(x + S.Half) - (g(x) - g(x - 1))*f(x - S.Half)
152
+ assert res3 == ref3
153
+
154
+ res4 = differentiate_finite(f(x)*g(x).diff(x).diff(x), x)
155
+ ref4 = -((g(x - Rational(3, 2)) - 2*g(x - S.Half) + g(x + S.Half))*f(x - S.Half)) \
156
+ + (g(x - S.Half) - 2*g(x + S.Half) + g(x + Rational(3, 2)))*f(x + S.Half)
157
+ assert res4 == ref4
158
+
159
+ res5_expr = f(x).diff(x)*g(x).diff(x)
160
+ res5 = differentiate_finite(res5_expr, points=[x-h, x, x+h])
161
+ ref5 = (-2*f(x)/h + f(-h + x)/(2*h) + 3*f(h + x)/(2*h))*(-2*g(x)/h + g(-h + x)/(2*h) \
162
+ + 3*g(h + x)/(2*h))/(2*h) - (2*f(x)/h - 3*f(-h + x)/(2*h) - \
163
+ f(h + x)/(2*h))*(2*g(x)/h - 3*g(-h + x)/(2*h) - g(h + x)/(2*h))/(2*h)
164
+ assert res5 == ref5
165
+
166
+ res6 = res5.limit(h, 0).doit()
167
+ ref6 = diff(res5_expr, x)
168
+ assert res6 == ref6
lib/python3.12/site-packages/sympy/calculus/tests/test_singularities.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.numbers import (I, Rational, pi, oo)
2
+ from sympy.core.singleton import S
3
+ from sympy.core.symbol import Symbol, Dummy
4
+ from sympy.core.function import Lambda
5
+ from sympy.functions.elementary.exponential import (exp, log)
6
+ from sympy.functions.elementary.trigonometric import sec, csc
7
+ from sympy.functions.elementary.hyperbolic import (coth, sech,
8
+ atanh, asech, acoth, acsch)
9
+ from sympy.functions.elementary.miscellaneous import sqrt
10
+ from sympy.calculus.singularities import (
11
+ singularities,
12
+ is_increasing,
13
+ is_strictly_increasing,
14
+ is_decreasing,
15
+ is_strictly_decreasing,
16
+ is_monotonic
17
+ )
18
+ from sympy.sets import Interval, FiniteSet, Union, ImageSet
19
+ from sympy.testing.pytest import raises
20
+ from sympy.abc import x, y
21
+
22
+
23
+ def test_singularities():
24
+ x = Symbol('x')
25
+ assert singularities(x**2, x) == S.EmptySet
26
+ assert singularities(x/(x**2 + 3*x + 2), x) == FiniteSet(-2, -1)
27
+ assert singularities(1/(x**2 + 1), x) == FiniteSet(I, -I)
28
+ assert singularities(x/(x**3 + 1), x) == \
29
+ FiniteSet(-1, (1 - sqrt(3) * I) / 2, (1 + sqrt(3) * I) / 2)
30
+ assert singularities(1/(y**2 + 2*I*y + 1), y) == \
31
+ FiniteSet(-I + sqrt(2)*I, -I - sqrt(2)*I)
32
+ _n = Dummy('n')
33
+ assert singularities(sech(x), x).dummy_eq(Union(
34
+ ImageSet(Lambda(_n, 2*_n*I*pi + I*pi/2), S.Integers),
35
+ ImageSet(Lambda(_n, 2*_n*I*pi + 3*I*pi/2), S.Integers)))
36
+ assert singularities(coth(x), x).dummy_eq(Union(
37
+ ImageSet(Lambda(_n, 2*_n*I*pi + I*pi), S.Integers),
38
+ ImageSet(Lambda(_n, 2*_n*I*pi), S.Integers)))
39
+ assert singularities(atanh(x), x) == FiniteSet(-1, 1)
40
+ assert singularities(acoth(x), x) == FiniteSet(-1, 1)
41
+ assert singularities(asech(x), x) == FiniteSet(0)
42
+ assert singularities(acsch(x), x) == FiniteSet(0)
43
+
44
+ x = Symbol('x', real=True)
45
+ assert singularities(1/(x**2 + 1), x) == S.EmptySet
46
+ assert singularities(exp(1/x), x, S.Reals) == FiniteSet(0)
47
+ assert singularities(exp(1/x), x, Interval(1, 2)) == S.EmptySet
48
+ assert singularities(log((x - 2)**2), x, Interval(1, 3)) == FiniteSet(2)
49
+ raises(NotImplementedError, lambda: singularities(x**-oo, x))
50
+ assert singularities(sec(x), x, Interval(0, 3*pi)) == FiniteSet(
51
+ pi/2, 3*pi/2, 5*pi/2)
52
+ assert singularities(csc(x), x, Interval(0, 3*pi)) == FiniteSet(
53
+ 0, pi, 2*pi, 3*pi)
54
+
55
+
56
+ def test_is_increasing():
57
+ """Test whether is_increasing returns correct value."""
58
+ a = Symbol('a', negative=True)
59
+
60
+ assert is_increasing(x**3 - 3*x**2 + 4*x, S.Reals)
61
+ assert is_increasing(-x**2, Interval(-oo, 0))
62
+ assert not is_increasing(-x**2, Interval(0, oo))
63
+ assert not is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3))
64
+ assert is_increasing(x**2 + y, Interval(1, oo), x)
65
+ assert is_increasing(-x**2*a, Interval(1, oo), x)
66
+ assert is_increasing(1)
67
+
68
+ assert is_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval(-2, 3)) is False
69
+
70
+
71
+ def test_is_strictly_increasing():
72
+ """Test whether is_strictly_increasing returns correct value."""
73
+ assert is_strictly_increasing(
74
+ 4*x**3 - 6*x**2 - 72*x + 30, Interval.Ropen(-oo, -2))
75
+ assert is_strictly_increasing(
76
+ 4*x**3 - 6*x**2 - 72*x + 30, Interval.Lopen(3, oo))
77
+ assert not is_strictly_increasing(
78
+ 4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3))
79
+ assert not is_strictly_increasing(-x**2, Interval(0, oo))
80
+ assert not is_strictly_decreasing(1)
81
+
82
+ assert is_strictly_increasing(4*x**3 - 6*x**2 - 72*x + 30, Interval.open(-2, 3)) is False
83
+
84
+
85
+ def test_is_decreasing():
86
+ """Test whether is_decreasing returns correct value."""
87
+ b = Symbol('b', positive=True)
88
+
89
+ assert is_decreasing(1/(x**2 - 3*x), Interval.open(Rational(3,2), 3))
90
+ assert is_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3))
91
+ assert is_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
92
+ assert not is_decreasing(1/(x**2 - 3*x), Interval.Ropen(-oo, Rational(3, 2)))
93
+ assert not is_decreasing(-x**2, Interval(-oo, 0))
94
+ assert not is_decreasing(-x**2*b, Interval(-oo, 0), x)
95
+
96
+
97
+ def test_is_strictly_decreasing():
98
+ """Test whether is_strictly_decreasing returns correct value."""
99
+ assert is_strictly_decreasing(1/(x**2 - 3*x), Interval.Lopen(3, oo))
100
+ assert not is_strictly_decreasing(
101
+ 1/(x**2 - 3*x), Interval.Ropen(-oo, Rational(3, 2)))
102
+ assert not is_strictly_decreasing(-x**2, Interval(-oo, 0))
103
+ assert not is_strictly_decreasing(1)
104
+ assert is_strictly_decreasing(1/(x**2 - 3*x), Interval.open(Rational(3,2), 3))
105
+ assert is_strictly_decreasing(1/(x**2 - 3*x), Interval.open(1.5, 3))
106
+
107
+
108
+ def test_is_monotonic():
109
+ """Test whether is_monotonic returns correct value."""
110
+ assert is_monotonic(1/(x**2 - 3*x), Interval.open(Rational(3,2), 3))
111
+ assert is_monotonic(1/(x**2 - 3*x), Interval.open(1.5, 3))
112
+ assert is_monotonic(1/(x**2 - 3*x), Interval.Lopen(3, oo))
113
+ assert is_monotonic(x**3 - 3*x**2 + 4*x, S.Reals)
114
+ assert not is_monotonic(-x**2, S.Reals)
115
+ assert is_monotonic(x**2 + y + 1, Interval(1, 2), x)
116
+ raises(NotImplementedError, lambda: is_monotonic(x**2 + y + 1))
117
+
118
+
119
+ def test_issue_23401():
120
+ x = Symbol('x')
121
+ expr = (x + 1)/(-1.0e-3*x**2 + 0.1*x + 0.1)
122
+ assert is_increasing(expr, Interval(1,2), x)
lib/python3.12/site-packages/sympy/calculus/tests/test_util.py ADDED
@@ -0,0 +1,392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.function import Lambda
2
+ from sympy.core.numbers import (E, I, Rational, oo, pi)
3
+ from sympy.core.relational import Eq
4
+ from sympy.core.singleton import S
5
+ from sympy.core.symbol import (Dummy, Symbol)
6
+ from sympy.functions.elementary.complexes import (Abs, re)
7
+ from sympy.functions.elementary.exponential import (exp, log)
8
+ from sympy.functions.elementary.integers import frac
9
+ from sympy.functions.elementary.miscellaneous import sqrt
10
+ from sympy.functions.elementary.piecewise import Piecewise
11
+ from sympy.functions.elementary.trigonometric import (
12
+ cos, cot, csc, sec, sin, tan, asin, acos, atan, acot, asec, acsc)
13
+ from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth,
14
+ sech, csch, asinh, acosh, atanh, acoth, asech, acsch)
15
+ from sympy.functions.special.gamma_functions import gamma
16
+ from sympy.functions.special.error_functions import expint
17
+ from sympy.matrices.expressions.matexpr import MatrixSymbol
18
+ from sympy.simplify.simplify import simplify
19
+ from sympy.calculus.util import (function_range, continuous_domain, not_empty_in,
20
+ periodicity, lcim, is_convex,
21
+ stationary_points, minimum, maximum)
22
+ from sympy.sets.sets import (Interval, FiniteSet, Complement, Union)
23
+ from sympy.sets.fancysets import ImageSet
24
+ from sympy.sets.conditionset import ConditionSet
25
+ from sympy.testing.pytest import XFAIL, raises, _both_exp_pow, slow
26
+ from sympy.abc import x, y
27
+
28
+ a = Symbol('a', real=True)
29
+
30
+ def test_function_range():
31
+ assert function_range(sin(x), x, Interval(-pi/2, pi/2)
32
+ ) == Interval(-1, 1)
33
+ assert function_range(sin(x), x, Interval(0, pi)
34
+ ) == Interval(0, 1)
35
+ assert function_range(tan(x), x, Interval(0, pi)
36
+ ) == Interval(-oo, oo)
37
+ assert function_range(tan(x), x, Interval(pi/2, pi)
38
+ ) == Interval(-oo, 0)
39
+ assert function_range((x + 3)/(x - 2), x, Interval(-5, 5)
40
+ ) == Union(Interval(-oo, Rational(2, 7)), Interval(Rational(8, 3), oo))
41
+ assert function_range(1/(x**2), x, Interval(-1, 1)
42
+ ) == Interval(1, oo)
43
+ assert function_range(exp(x), x, Interval(-1, 1)
44
+ ) == Interval(exp(-1), exp(1))
45
+ assert function_range(log(x) - x, x, S.Reals
46
+ ) == Interval(-oo, -1)
47
+ assert function_range(sqrt(3*x - 1), x, Interval(0, 2)
48
+ ) == Interval(0, sqrt(5))
49
+ assert function_range(x*(x - 1) - (x**2 - x), x, S.Reals
50
+ ) == FiniteSet(0)
51
+ assert function_range(x*(x - 1) - (x**2 - x) + y, x, S.Reals
52
+ ) == FiniteSet(y)
53
+ assert function_range(sin(x), x, Union(Interval(-5, -3), FiniteSet(4))
54
+ ) == Union(Interval(-sin(3), 1), FiniteSet(sin(4)))
55
+ assert function_range(cos(x), x, Interval(-oo, -4)
56
+ ) == Interval(-1, 1)
57
+ assert function_range(cos(x), x, S.EmptySet) == S.EmptySet
58
+ assert function_range(x/sqrt(x**2+1), x, S.Reals) == Interval.open(-1,1)
59
+ raises(NotImplementedError, lambda : function_range(
60
+ exp(x)*(sin(x) - cos(x))/2 - x, x, S.Reals))
61
+ raises(NotImplementedError, lambda : function_range(
62
+ sin(x) + x, x, S.Reals)) # issue 13273
63
+ raises(NotImplementedError, lambda : function_range(
64
+ log(x), x, S.Integers))
65
+ raises(NotImplementedError, lambda : function_range(
66
+ sin(x)/2, x, S.Naturals))
67
+
68
+
69
+ @slow
70
+ def test_function_range1():
71
+ assert function_range(tan(x)**2 + tan(3*x)**2 + 1, x, S.Reals) == Interval(1,oo)
72
+
73
+
74
+ def test_continuous_domain():
75
+ assert continuous_domain(sin(x), x, Interval(0, 2*pi)) == Interval(0, 2*pi)
76
+ assert continuous_domain(tan(x), x, Interval(0, 2*pi)) == \
77
+ Union(Interval(0, pi/2, False, True), Interval(pi/2, pi*Rational(3, 2), True, True),
78
+ Interval(pi*Rational(3, 2), 2*pi, True, False))
79
+ assert continuous_domain(cot(x), x, Interval(0, 2*pi)) == Union(
80
+ Interval.open(0, pi), Interval.open(pi, 2*pi))
81
+ assert continuous_domain((x - 1)/((x - 1)**2), x, S.Reals) == \
82
+ Union(Interval(-oo, 1, True, True), Interval(1, oo, True, True))
83
+ assert continuous_domain(log(x) + log(4*x - 1), x, S.Reals) == \
84
+ Interval(Rational(1, 4), oo, True, True)
85
+ assert continuous_domain(1/sqrt(x - 3), x, S.Reals) == Interval(3, oo, True, True)
86
+ assert continuous_domain(1/x - 2, x, S.Reals) == \
87
+ Union(Interval.open(-oo, 0), Interval.open(0, oo))
88
+ assert continuous_domain(1/(x**2 - 4) + 2, x, S.Reals) == \
89
+ Union(Interval.open(-oo, -2), Interval.open(-2, 2), Interval.open(2, oo))
90
+ assert continuous_domain((x+1)**pi, x, S.Reals) == Interval(-1, oo)
91
+ assert continuous_domain((x+1)**(pi/2), x, S.Reals) == Interval(-1, oo)
92
+ assert continuous_domain(x**x, x, S.Reals) == Interval(0, oo)
93
+ assert continuous_domain((x+1)**log(x**2), x, S.Reals) == Union(
94
+ Interval.Ropen(-1, 0), Interval.open(0, oo))
95
+ domain = continuous_domain(log(tan(x)**2 + 1), x, S.Reals)
96
+ assert not domain.contains(3*pi/2)
97
+ assert domain.contains(5)
98
+ d = Symbol('d', even=True, zero=False)
99
+ assert continuous_domain(x**(1/d), x, S.Reals) == Interval(0, oo)
100
+ n = Dummy('n')
101
+ assert continuous_domain(1/sin(x), x, S.Reals).dummy_eq(Complement(
102
+ S.Reals, Union(ImageSet(Lambda(n, 2*n*pi + pi), S.Integers),
103
+ ImageSet(Lambda(n, 2*n*pi), S.Integers))))
104
+ assert continuous_domain(sin(x) + cos(x), x, S.Reals) == S.Reals
105
+ assert continuous_domain(asin(x), x, S.Reals) == Interval(-1, 1) # issue #21786
106
+ assert continuous_domain(1/acos(log(x)), x, S.Reals) == Interval.Ropen(exp(-1), E)
107
+ assert continuous_domain(sinh(x)+cosh(x), x, S.Reals) == S.Reals
108
+ assert continuous_domain(tanh(x)+sech(x), x, S.Reals) == S.Reals
109
+ assert continuous_domain(atan(x)+asinh(x), x, S.Reals) == S.Reals
110
+ assert continuous_domain(acosh(x), x, S.Reals) == Interval(1, oo)
111
+ assert continuous_domain(atanh(x), x, S.Reals) == Interval.open(-1, 1)
112
+ assert continuous_domain(atanh(x)+acosh(x), x, S.Reals) == S.EmptySet
113
+ assert continuous_domain(asech(x), x, S.Reals) == Interval.Lopen(0, 1)
114
+ assert continuous_domain(acoth(x), x, S.Reals) == Union(
115
+ Interval.open(-oo, -1), Interval.open(1, oo))
116
+ assert continuous_domain(asec(x), x, S.Reals) == Union(
117
+ Interval(-oo, -1), Interval(1, oo))
118
+ assert continuous_domain(acsc(x), x, S.Reals) == Union(
119
+ Interval(-oo, -1), Interval(1, oo))
120
+ for f in (coth, acsch, csch):
121
+ assert continuous_domain(f(x), x, S.Reals) == Union(
122
+ Interval.open(-oo, 0), Interval.open(0, oo))
123
+ assert continuous_domain(acot(x), x, S.Reals).contains(0) == False
124
+ assert continuous_domain(1/(exp(x) - x), x, S.Reals) == Complement(
125
+ S.Reals, ConditionSet(x, Eq(-x + exp(x), 0), S.Reals))
126
+ assert continuous_domain(frac(x**2), x, Interval(-2,-1)) == Union(
127
+ Interval.open(-2, -sqrt(3)), Interval.open(-sqrt(2), -1),
128
+ Interval.open(-sqrt(3), -sqrt(2)))
129
+ assert continuous_domain(frac(x), x, S.Reals) == Complement(
130
+ S.Reals, S.Integers)
131
+ raises(NotImplementedError, lambda : continuous_domain(
132
+ 1/(x**2+1), x, S.Complexes))
133
+ raises(NotImplementedError, lambda : continuous_domain(
134
+ gamma(x), x, Interval(-5,0)))
135
+ assert continuous_domain(x + gamma(pi), x, S.Reals) == S.Reals
136
+
137
+
138
+ @XFAIL
139
+ def test_continuous_domain_acot():
140
+ acot_cont = Piecewise((pi+acot(x), x<0), (acot(x), True))
141
+ assert continuous_domain(acot_cont, x, S.Reals) == S.Reals
142
+
143
+ @XFAIL
144
+ def test_continuous_domain_gamma():
145
+ assert continuous_domain(gamma(x), x, S.Reals).contains(-1) == False
146
+
147
+ @XFAIL
148
+ def test_continuous_domain_neg_power():
149
+ assert continuous_domain((x-2)**(1-x), x, S.Reals) == Interval.open(2, oo)
150
+
151
+
152
+ def test_not_empty_in():
153
+ assert not_empty_in(FiniteSet(x, 2*x).intersect(Interval(1, 2, True, False)), x) == \
154
+ Interval(S.Half, 2, True, False)
155
+ assert not_empty_in(FiniteSet(x, x**2).intersect(Interval(1, 2)), x) == \
156
+ Union(Interval(-sqrt(2), -1), Interval(1, 2))
157
+ assert not_empty_in(FiniteSet(x**2 + x, x).intersect(Interval(2, 4)), x) == \
158
+ Union(Interval(-sqrt(17)/2 - S.Half, -2),
159
+ Interval(1, Rational(-1, 2) + sqrt(17)/2), Interval(2, 4))
160
+ assert not_empty_in(FiniteSet(x/(x - 1)).intersect(S.Reals), x) == \
161
+ Complement(S.Reals, FiniteSet(1))
162
+ assert not_empty_in(FiniteSet(a/(a - 1)).intersect(S.Reals), a) == \
163
+ Complement(S.Reals, FiniteSet(1))
164
+ assert not_empty_in(FiniteSet((x**2 - 3*x + 2)/(x - 1)).intersect(S.Reals), x) == \
165
+ Complement(S.Reals, FiniteSet(1))
166
+ assert not_empty_in(FiniteSet(3, 4, x/(x - 1)).intersect(Interval(2, 3)), x) == \
167
+ Interval(-oo, oo)
168
+ assert not_empty_in(FiniteSet(4, x/(x - 1)).intersect(Interval(2, 3)), x) == \
169
+ Interval(S(3)/2, 2)
170
+ assert not_empty_in(FiniteSet(x/(x**2 - 1)).intersect(S.Reals), x) == \
171
+ Complement(S.Reals, FiniteSet(-1, 1))
172
+ assert not_empty_in(FiniteSet(x, x**2).intersect(Union(Interval(1, 3, True, True),
173
+ Interval(4, 5))), x) == \
174
+ Union(Interval(-sqrt(5), -2), Interval(-sqrt(3), -1, True, True),
175
+ Interval(1, 3, True, True), Interval(4, 5))
176
+ assert not_empty_in(FiniteSet(1).intersect(Interval(3, 4)), x) == S.EmptySet
177
+ assert not_empty_in(FiniteSet(x**2/(x + 2)).intersect(Interval(1, oo)), x) == \
178
+ Union(Interval(-2, -1, True, False), Interval(2, oo))
179
+ raises(ValueError, lambda: not_empty_in(x))
180
+ raises(ValueError, lambda: not_empty_in(Interval(0, 1), x))
181
+ raises(NotImplementedError,
182
+ lambda: not_empty_in(FiniteSet(x).intersect(S.Reals), x, a))
183
+
184
+
185
+ @_both_exp_pow
186
+ def test_periodicity():
187
+ assert periodicity(sin(2*x), x) == pi
188
+ assert periodicity((-2)*tan(4*x), x) == pi/4
189
+ assert periodicity(sin(x)**2, x) == 2*pi
190
+ assert periodicity(3**tan(3*x), x) == pi/3
191
+ assert periodicity(tan(x)*cos(x), x) == 2*pi
192
+ assert periodicity(sin(x)**(tan(x)), x) == 2*pi
193
+ assert periodicity(tan(x)*sec(x), x) == 2*pi
194
+ assert periodicity(sin(2*x)*cos(2*x) - y, x) == pi/2
195
+ assert periodicity(tan(x) + cot(x), x) == pi
196
+ assert periodicity(sin(x) - cos(2*x), x) == 2*pi
197
+ assert periodicity(sin(x) - 1, x) == 2*pi
198
+ assert periodicity(sin(4*x) + sin(x)*cos(x), x) == pi
199
+ assert periodicity(exp(sin(x)), x) == 2*pi
200
+ assert periodicity(log(cot(2*x)) - sin(cos(2*x)), x) == pi
201
+ assert periodicity(sin(2*x)*exp(tan(x) - csc(2*x)), x) == pi
202
+ assert periodicity(cos(sec(x) - csc(2*x)), x) == 2*pi
203
+ assert periodicity(tan(sin(2*x)), x) == pi
204
+ assert periodicity(2*tan(x)**2, x) == pi
205
+ assert periodicity(sin(x%4), x) == 4
206
+ assert periodicity(sin(x)%4, x) == 2*pi
207
+ assert periodicity(tan((3*x-2)%4), x) == Rational(4, 3)
208
+ assert periodicity((sqrt(2)*(x+1)+x) % 3, x) == 3 / (sqrt(2)+1)
209
+ assert periodicity((x**2+1) % x, x) is None
210
+ assert periodicity(sin(re(x)), x) == 2*pi
211
+ assert periodicity(sin(x)**2 + cos(x)**2, x) is S.Zero
212
+ assert periodicity(tan(x), y) is S.Zero
213
+ assert periodicity(sin(x) + I*cos(x), x) == 2*pi
214
+ assert periodicity(x - sin(2*y), y) == pi
215
+
216
+ assert periodicity(exp(x), x) is None
217
+ assert periodicity(exp(I*x), x) == 2*pi
218
+ assert periodicity(exp(I*a), a) == 2*pi
219
+ assert periodicity(exp(a), a) is None
220
+ assert periodicity(exp(log(sin(a) + I*cos(2*a)), evaluate=False), a) == 2*pi
221
+ assert periodicity(exp(log(sin(2*a) + I*cos(a)), evaluate=False), a) == 2*pi
222
+ assert periodicity(exp(sin(a)), a) == 2*pi
223
+ assert periodicity(exp(2*I*a), a) == pi
224
+ assert periodicity(exp(a + I*sin(a)), a) is None
225
+ assert periodicity(exp(cos(a/2) + sin(a)), a) == 4*pi
226
+ assert periodicity(log(x), x) is None
227
+ assert periodicity(exp(x)**sin(x), x) is None
228
+ assert periodicity(sin(x)**y, y) is None
229
+
230
+ assert periodicity(Abs(sin(Abs(sin(x)))), x) == pi
231
+ assert all(periodicity(Abs(f(x)), x) == pi for f in (
232
+ cos, sin, sec, csc, tan, cot))
233
+ assert periodicity(Abs(sin(tan(x))), x) == pi
234
+ assert periodicity(Abs(sin(sin(x) + tan(x))), x) == 2*pi
235
+ assert periodicity(sin(x) > S.Half, x) == 2*pi
236
+
237
+ assert periodicity(x > 2, x) is None
238
+ assert periodicity(x**3 - x**2 + 1, x) is None
239
+ assert periodicity(Abs(x), x) is None
240
+ assert periodicity(Abs(x**2 - 1), x) is None
241
+
242
+ assert periodicity((x**2 + 4)%2, x) is None
243
+ assert periodicity((E**x)%3, x) is None
244
+
245
+ assert periodicity(sin(expint(1, x))/expint(1, x), x) is None
246
+ # returning `None` for any Piecewise
247
+ p = Piecewise((0, x < -1), (x**2, x <= 1), (log(x), True))
248
+ assert periodicity(p, x) is None
249
+
250
+ m = MatrixSymbol('m', 3, 3)
251
+ raises(NotImplementedError, lambda: periodicity(sin(m), m))
252
+ raises(NotImplementedError, lambda: periodicity(sin(m[0, 0]), m))
253
+ raises(NotImplementedError, lambda: periodicity(sin(m), m[0, 0]))
254
+ raises(NotImplementedError, lambda: periodicity(sin(m[0, 0]), m[0, 0]))
255
+
256
+
257
+ def test_periodicity_check():
258
+ assert periodicity(tan(x), x, check=True) == pi
259
+ assert periodicity(sin(x) + cos(x), x, check=True) == 2*pi
260
+ assert periodicity(sec(x), x) == 2*pi
261
+ assert periodicity(sin(x*y), x) == 2*pi/abs(y)
262
+ assert periodicity(Abs(sec(sec(x))), x) == pi
263
+
264
+
265
+ def test_lcim():
266
+ assert lcim([S.Half, S(2), S(3)]) == 6
267
+ assert lcim([pi/2, pi/4, pi]) == pi
268
+ assert lcim([2*pi, pi/2]) == 2*pi
269
+ assert lcim([S.One, 2*pi]) is None
270
+ assert lcim([S(2) + 2*E, E/3 + Rational(1, 3), S.One + E]) == S(2) + 2*E
271
+
272
+
273
+ def test_is_convex():
274
+ assert is_convex(1/x, x, domain=Interval.open(0, oo)) == True
275
+ assert is_convex(1/x, x, domain=Interval(-oo, 0)) == False
276
+ assert is_convex(x**2, x, domain=Interval(0, oo)) == True
277
+ assert is_convex(1/x**3, x, domain=Interval.Lopen(0, oo)) == True
278
+ assert is_convex(-1/x**3, x, domain=Interval.Ropen(-oo, 0)) == True
279
+ assert is_convex(log(x) ,x) == False
280
+ assert is_convex(x**2+y**2, x, y) == True
281
+ assert is_convex(cos(x) + cos(y), x) == False
282
+ assert is_convex(8*x**2 - 2*y**2, x, y) == False
283
+
284
+
285
+ def test_stationary_points():
286
+ assert stationary_points(sin(x), x, Interval(-pi/2, pi/2)
287
+ ) == {-pi/2, pi/2}
288
+ assert stationary_points(sin(x), x, Interval.Ropen(0, pi/4)
289
+ ) is S.EmptySet
290
+ assert stationary_points(tan(x), x,
291
+ ) is S.EmptySet
292
+ assert stationary_points(sin(x)*cos(x), x, Interval(0, pi)
293
+ ) == {pi/4, pi*Rational(3, 4)}
294
+ assert stationary_points(sec(x), x, Interval(0, pi)
295
+ ) == {0, pi}
296
+ assert stationary_points((x+3)*(x-2), x
297
+ ) == FiniteSet(Rational(-1, 2))
298
+ assert stationary_points((x + 3)/(x - 2), x, Interval(-5, 5)
299
+ ) is S.EmptySet
300
+ assert stationary_points((x**2+3)/(x-2), x
301
+ ) == {2 - sqrt(7), 2 + sqrt(7)}
302
+ assert stationary_points((x**2+3)/(x-2), x, Interval(0, 5)
303
+ ) == {2 + sqrt(7)}
304
+ assert stationary_points(x**4 + x**3 - 5*x**2, x, S.Reals
305
+ ) == FiniteSet(-2, 0, Rational(5, 4))
306
+ assert stationary_points(exp(x), x
307
+ ) is S.EmptySet
308
+ assert stationary_points(log(x) - x, x, S.Reals
309
+ ) == {1}
310
+ assert stationary_points(cos(x), x, Union(Interval(0, 5), Interval(-6, -3))
311
+ ) == {0, -pi, pi}
312
+ assert stationary_points(y, x, S.Reals
313
+ ) == S.Reals
314
+ assert stationary_points(y, x, S.EmptySet) == S.EmptySet
315
+
316
+
317
+ def test_maximum():
318
+ assert maximum(sin(x), x) is S.One
319
+ assert maximum(sin(x), x, Interval(0, 1)) == sin(1)
320
+ assert maximum(tan(x), x) is oo
321
+ assert maximum(tan(x), x, Interval(-pi/4, pi/4)) is S.One
322
+ assert maximum(sin(x)*cos(x), x, S.Reals) == S.Half
323
+ assert simplify(maximum(sin(x)*cos(x), x, Interval(pi*Rational(3, 8), pi*Rational(5, 8)))
324
+ ) == sqrt(2)/4
325
+ assert maximum((x+3)*(x-2), x) is oo
326
+ assert maximum((x+3)*(x-2), x, Interval(-5, 0)) == S(14)
327
+ assert maximum((x+3)/(x-2), x, Interval(-5, 0)) == Rational(2, 7)
328
+ assert simplify(maximum(-x**4-x**3+x**2+10, x)
329
+ ) == 41*sqrt(41)/512 + Rational(5419, 512)
330
+ assert maximum(exp(x), x, Interval(-oo, 2)) == exp(2)
331
+ assert maximum(log(x) - x, x, S.Reals) is S.NegativeOne
332
+ assert maximum(cos(x), x, Union(Interval(0, 5), Interval(-6, -3))
333
+ ) is S.One
334
+ assert maximum(cos(x)-sin(x), x, S.Reals) == sqrt(2)
335
+ assert maximum(y, x, S.Reals) == y
336
+ assert maximum(abs(a**3 + a), a, Interval(0, 2)) == 10
337
+ assert maximum(abs(60*a**3 + 24*a), a, Interval(0, 2)) == 528
338
+ assert maximum(abs(12*a*(5*a**2 + 2)), a, Interval(0, 2)) == 528
339
+ assert maximum(x/sqrt(x**2+1), x, S.Reals) == 1
340
+
341
+ raises(ValueError, lambda : maximum(sin(x), x, S.EmptySet))
342
+ raises(ValueError, lambda : maximum(log(cos(x)), x, S.EmptySet))
343
+ raises(ValueError, lambda : maximum(1/(x**2 + y**2 + 1), x, S.EmptySet))
344
+ raises(ValueError, lambda : maximum(sin(x), sin(x)))
345
+ raises(ValueError, lambda : maximum(sin(x), x*y, S.EmptySet))
346
+ raises(ValueError, lambda : maximum(sin(x), S.One))
347
+
348
+
349
+ def test_minimum():
350
+ assert minimum(sin(x), x) is S.NegativeOne
351
+ assert minimum(sin(x), x, Interval(1, 4)) == sin(4)
352
+ assert minimum(tan(x), x) is -oo
353
+ assert minimum(tan(x), x, Interval(-pi/4, pi/4)) is S.NegativeOne
354
+ assert minimum(sin(x)*cos(x), x, S.Reals) == Rational(-1, 2)
355
+ assert simplify(minimum(sin(x)*cos(x), x, Interval(pi*Rational(3, 8), pi*Rational(5, 8)))
356
+ ) == -sqrt(2)/4
357
+ assert minimum((x+3)*(x-2), x) == Rational(-25, 4)
358
+ assert minimum((x+3)/(x-2), x, Interval(-5, 0)) == Rational(-3, 2)
359
+ assert minimum(x**4-x**3+x**2+10, x) == S(10)
360
+ assert minimum(exp(x), x, Interval(-2, oo)) == exp(-2)
361
+ assert minimum(log(x) - x, x, S.Reals) is -oo
362
+ assert minimum(cos(x), x, Union(Interval(0, 5), Interval(-6, -3))
363
+ ) is S.NegativeOne
364
+ assert minimum(cos(x)-sin(x), x, S.Reals) == -sqrt(2)
365
+ assert minimum(y, x, S.Reals) == y
366
+ assert minimum(x/sqrt(x**2+1), x, S.Reals) == -1
367
+
368
+ raises(ValueError, lambda : minimum(sin(x), x, S.EmptySet))
369
+ raises(ValueError, lambda : minimum(log(cos(x)), x, S.EmptySet))
370
+ raises(ValueError, lambda : minimum(1/(x**2 + y**2 + 1), x, S.EmptySet))
371
+ raises(ValueError, lambda : minimum(sin(x), sin(x)))
372
+ raises(ValueError, lambda : minimum(sin(x), x*y, S.EmptySet))
373
+ raises(ValueError, lambda : minimum(sin(x), S.One))
374
+
375
+
376
+ def test_issue_19869():
377
+ assert (maximum(sqrt(3)*(x - 1)/(3*sqrt(x**2 + 1)), x)
378
+ ) == sqrt(3)/3
379
+
380
+
381
+ def test_issue_16469():
382
+ f = abs(a)
383
+ assert function_range(f, a, S.Reals) == Interval(0, oo, False, True)
384
+
385
+
386
+ @_both_exp_pow
387
+ def test_issue_18747():
388
+ assert periodicity(exp(pi*I*(x/4 + S.Half/2)), x) == 8
389
+
390
+
391
+ def test_issue_25942():
392
+ assert (acos(x) > pi/3).as_set() == Interval.Ropen(-1, S(1)/2)
lib/python3.12/site-packages/sympy/calculus/util.py ADDED
@@ -0,0 +1,905 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .accumulationbounds import AccumBounds, AccumulationBounds # noqa: F401
2
+ from .singularities import singularities
3
+ from sympy.core import Pow, S
4
+ from sympy.core.function import diff, expand_mul, Function
5
+ from sympy.core.kind import NumberKind
6
+ from sympy.core.mod import Mod
7
+ from sympy.core.numbers import equal_valued
8
+ from sympy.core.relational import Relational
9
+ from sympy.core.symbol import Symbol, Dummy
10
+ from sympy.core.sympify import _sympify
11
+ from sympy.functions.elementary.complexes import Abs, im, re
12
+ from sympy.functions.elementary.exponential import exp, log
13
+ from sympy.functions.elementary.integers import frac
14
+ from sympy.functions.elementary.piecewise import Piecewise
15
+ from sympy.functions.elementary.trigonometric import (
16
+ TrigonometricFunction, sin, cos, tan, cot, csc, sec,
17
+ asin, acos, acot, atan, asec, acsc)
18
+ from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth,
19
+ sech, csch, asinh, acosh, atanh, acoth, asech, acsch)
20
+ from sympy.polys.polytools import degree, lcm_list
21
+ from sympy.sets.sets import (Interval, Intersection, FiniteSet, Union,
22
+ Complement)
23
+ from sympy.sets.fancysets import ImageSet
24
+ from sympy.sets.conditionset import ConditionSet
25
+ from sympy.utilities import filldedent
26
+ from sympy.utilities.iterables import iterable
27
+ from sympy.matrices.dense import hessian
28
+
29
+
30
+ def continuous_domain(f, symbol, domain):
31
+ """
32
+ Returns the domain on which the function expression f is continuous.
33
+
34
+ This function is limited by the ability to determine the various
35
+ singularities and discontinuities of the given function.
36
+ The result is either given as a union of intervals or constructed using
37
+ other set operations.
38
+
39
+ Parameters
40
+ ==========
41
+
42
+ f : :py:class:`~.Expr`
43
+ The concerned function.
44
+ symbol : :py:class:`~.Symbol`
45
+ The variable for which the intervals are to be determined.
46
+ domain : :py:class:`~.Interval`
47
+ The domain over which the continuity of the symbol has to be checked.
48
+
49
+ Examples
50
+ ========
51
+
52
+ >>> from sympy import Interval, Symbol, S, tan, log, pi, sqrt
53
+ >>> from sympy.calculus.util import continuous_domain
54
+ >>> x = Symbol('x')
55
+ >>> continuous_domain(1/x, x, S.Reals)
56
+ Union(Interval.open(-oo, 0), Interval.open(0, oo))
57
+ >>> continuous_domain(tan(x), x, Interval(0, pi))
58
+ Union(Interval.Ropen(0, pi/2), Interval.Lopen(pi/2, pi))
59
+ >>> continuous_domain(sqrt(x - 2), x, Interval(-5, 5))
60
+ Interval(2, 5)
61
+ >>> continuous_domain(log(2*x - 1), x, S.Reals)
62
+ Interval.open(1/2, oo)
63
+
64
+ Returns
65
+ =======
66
+
67
+ :py:class:`~.Interval`
68
+ Union of all intervals where the function is continuous.
69
+
70
+ Raises
71
+ ======
72
+
73
+ NotImplementedError
74
+ If the method to determine continuity of such a function
75
+ has not yet been developed.
76
+
77
+ """
78
+ from sympy.solvers.inequalities import solve_univariate_inequality
79
+
80
+ if not domain.is_subset(S.Reals):
81
+ raise NotImplementedError(filldedent('''
82
+ Domain must be a subset of S.Reals.
83
+ '''))
84
+ implemented = [Pow, exp, log, Abs, frac,
85
+ sin, cos, tan, cot, sec, csc,
86
+ asin, acos, atan, acot, asec, acsc,
87
+ sinh, cosh, tanh, coth, sech, csch,
88
+ asinh, acosh, atanh, acoth, asech, acsch]
89
+ used = [fct.func for fct in f.atoms(Function) if fct.has(symbol)]
90
+ if any(func not in implemented for func in used):
91
+ raise NotImplementedError(filldedent('''
92
+ Unable to determine the domain of the given function.
93
+ '''))
94
+
95
+ x = Symbol('x')
96
+ constraints = {
97
+ log: (x > 0,),
98
+ asin: (x >= -1, x <= 1),
99
+ acos: (x >= -1, x <= 1),
100
+ acosh: (x >= 1,),
101
+ atanh: (x > -1, x < 1),
102
+ asech: (x > 0, x <= 1)
103
+ }
104
+ constraints_union = {
105
+ asec: (x <= -1, x >= 1),
106
+ acsc: (x <= -1, x >= 1),
107
+ acoth: (x < -1, x > 1)
108
+ }
109
+
110
+ cont_domain = domain
111
+ for atom in f.atoms(Pow):
112
+ den = atom.exp.as_numer_denom()[1]
113
+ if atom.exp.is_rational and den.is_odd:
114
+ pass # 0**negative handled by singularities()
115
+ else:
116
+ constraint = solve_univariate_inequality(atom.base >= 0,
117
+ symbol).as_set()
118
+ cont_domain = Intersection(constraint, cont_domain)
119
+
120
+ for atom in f.atoms(Function):
121
+ if atom.func in constraints:
122
+ for c in constraints[atom.func]:
123
+ constraint_relational = c.subs(x, atom.args[0])
124
+ constraint_set = solve_univariate_inequality(
125
+ constraint_relational, symbol).as_set()
126
+ cont_domain = Intersection(constraint_set, cont_domain)
127
+ elif atom.func in constraints_union:
128
+ constraint_set = S.EmptySet
129
+ for c in constraints_union[atom.func]:
130
+ constraint_relational = c.subs(x, atom.args[0])
131
+ constraint_set += solve_univariate_inequality(
132
+ constraint_relational, symbol).as_set()
133
+ cont_domain = Intersection(constraint_set, cont_domain)
134
+ # XXX: the discontinuities below could be factored out in
135
+ # a new "discontinuities()".
136
+ elif atom.func == acot:
137
+ from sympy.solvers.solveset import solveset_real
138
+ # Sympy's acot() has a step discontinuity at 0. Since it's
139
+ # neither an essential singularity nor a pole, singularities()
140
+ # will not report it. But it's still relevant for determining
141
+ # the continuity of the function f.
142
+ cont_domain -= solveset_real(atom.args[0], symbol)
143
+ # Note that the above may introduce spurious discontinuities, e.g.
144
+ # for abs(acot(x)) at 0.
145
+ elif atom.func == frac:
146
+ from sympy.solvers.solveset import solveset_real
147
+ r = function_range(atom.args[0], symbol, domain)
148
+ r = Intersection(r, S.Integers)
149
+ if r.is_finite_set:
150
+ discont = S.EmptySet
151
+ for n in r:
152
+ discont += solveset_real(atom.args[0]-n, symbol)
153
+ else:
154
+ discont = ConditionSet(
155
+ symbol, S.Integers.contains(atom.args[0]), cont_domain)
156
+ cont_domain -= discont
157
+
158
+ return cont_domain - singularities(f, symbol, domain)
159
+
160
+
161
+ def function_range(f, symbol, domain):
162
+ """
163
+ Finds the range of a function in a given domain.
164
+ This method is limited by the ability to determine the singularities and
165
+ determine limits.
166
+
167
+ Parameters
168
+ ==========
169
+
170
+ f : :py:class:`~.Expr`
171
+ The concerned function.
172
+ symbol : :py:class:`~.Symbol`
173
+ The variable for which the range of function is to be determined.
174
+ domain : :py:class:`~.Interval`
175
+ The domain under which the range of the function has to be found.
176
+
177
+ Examples
178
+ ========
179
+
180
+ >>> from sympy import Interval, Symbol, S, exp, log, pi, sqrt, sin, tan
181
+ >>> from sympy.calculus.util import function_range
182
+ >>> x = Symbol('x')
183
+ >>> function_range(sin(x), x, Interval(0, 2*pi))
184
+ Interval(-1, 1)
185
+ >>> function_range(tan(x), x, Interval(-pi/2, pi/2))
186
+ Interval(-oo, oo)
187
+ >>> function_range(1/x, x, S.Reals)
188
+ Union(Interval.open(-oo, 0), Interval.open(0, oo))
189
+ >>> function_range(exp(x), x, S.Reals)
190
+ Interval.open(0, oo)
191
+ >>> function_range(log(x), x, S.Reals)
192
+ Interval(-oo, oo)
193
+ >>> function_range(sqrt(x), x, Interval(-5, 9))
194
+ Interval(0, 3)
195
+
196
+ Returns
197
+ =======
198
+
199
+ :py:class:`~.Interval`
200
+ Union of all ranges for all intervals under domain where function is
201
+ continuous.
202
+
203
+ Raises
204
+ ======
205
+
206
+ NotImplementedError
207
+ If any of the intervals, in the given domain, for which function
208
+ is continuous are not finite or real,
209
+ OR if the critical points of the function on the domain cannot be found.
210
+ """
211
+
212
+ if domain is S.EmptySet:
213
+ return S.EmptySet
214
+
215
+ period = periodicity(f, symbol)
216
+ if period == S.Zero:
217
+ # the expression is constant wrt symbol
218
+ return FiniteSet(f.expand())
219
+
220
+ from sympy.series.limits import limit
221
+ from sympy.solvers.solveset import solveset
222
+
223
+ if period is not None:
224
+ if isinstance(domain, Interval):
225
+ if (domain.inf - domain.sup).is_infinite:
226
+ domain = Interval(0, period)
227
+ elif isinstance(domain, Union):
228
+ for sub_dom in domain.args:
229
+ if isinstance(sub_dom, Interval) and \
230
+ ((sub_dom.inf - sub_dom.sup).is_infinite):
231
+ domain = Interval(0, period)
232
+
233
+ intervals = continuous_domain(f, symbol, domain)
234
+ range_int = S.EmptySet
235
+ if isinstance(intervals,(Interval, FiniteSet)):
236
+ interval_iter = (intervals,)
237
+
238
+ elif isinstance(intervals, Union):
239
+ interval_iter = intervals.args
240
+
241
+ else:
242
+ raise NotImplementedError(filldedent('''
243
+ Unable to find range for the given domain.
244
+ '''))
245
+
246
+ for interval in interval_iter:
247
+ if isinstance(interval, FiniteSet):
248
+ for singleton in interval:
249
+ if singleton in domain:
250
+ range_int += FiniteSet(f.subs(symbol, singleton))
251
+ elif isinstance(interval, Interval):
252
+ vals = S.EmptySet
253
+ critical_points = S.EmptySet
254
+ critical_values = S.EmptySet
255
+ bounds = ((interval.left_open, interval.inf, '+'),
256
+ (interval.right_open, interval.sup, '-'))
257
+
258
+ for is_open, limit_point, direction in bounds:
259
+ if is_open:
260
+ critical_values += FiniteSet(limit(f, symbol, limit_point, direction))
261
+ vals += critical_values
262
+
263
+ else:
264
+ vals += FiniteSet(f.subs(symbol, limit_point))
265
+
266
+ solution = solveset(f.diff(symbol), symbol, interval)
267
+
268
+ if not iterable(solution):
269
+ raise NotImplementedError(
270
+ 'Unable to find critical points for {}'.format(f))
271
+ if isinstance(solution, ImageSet):
272
+ raise NotImplementedError(
273
+ 'Infinite number of critical points for {}'.format(f))
274
+
275
+ critical_points += solution
276
+
277
+ for critical_point in critical_points:
278
+ vals += FiniteSet(f.subs(symbol, critical_point))
279
+
280
+ left_open, right_open = False, False
281
+
282
+ if critical_values is not S.EmptySet:
283
+ if critical_values.inf == vals.inf:
284
+ left_open = True
285
+
286
+ if critical_values.sup == vals.sup:
287
+ right_open = True
288
+
289
+ range_int += Interval(vals.inf, vals.sup, left_open, right_open)
290
+ else:
291
+ raise NotImplementedError(filldedent('''
292
+ Unable to find range for the given domain.
293
+ '''))
294
+
295
+ return range_int
296
+
297
+
298
+ def not_empty_in(finset_intersection, *syms):
299
+ """
300
+ Finds the domain of the functions in ``finset_intersection`` in which the
301
+ ``finite_set`` is not-empty.
302
+
303
+ Parameters
304
+ ==========
305
+
306
+ finset_intersection : Intersection of FiniteSet
307
+ The unevaluated intersection of FiniteSet containing
308
+ real-valued functions with Union of Sets
309
+ syms : Tuple of symbols
310
+ Symbol for which domain is to be found
311
+
312
+ Raises
313
+ ======
314
+
315
+ NotImplementedError
316
+ The algorithms to find the non-emptiness of the given FiniteSet are
317
+ not yet implemented.
318
+ ValueError
319
+ The input is not valid.
320
+ RuntimeError
321
+ It is a bug, please report it to the github issue tracker
322
+ (https://github.com/sympy/sympy/issues).
323
+
324
+ Examples
325
+ ========
326
+
327
+ >>> from sympy import FiniteSet, Interval, not_empty_in, oo
328
+ >>> from sympy.abc import x
329
+ >>> not_empty_in(FiniteSet(x/2).intersect(Interval(0, 1)), x)
330
+ Interval(0, 2)
331
+ >>> not_empty_in(FiniteSet(x, x**2).intersect(Interval(1, 2)), x)
332
+ Union(Interval(1, 2), Interval(-sqrt(2), -1))
333
+ >>> not_empty_in(FiniteSet(x**2/(x + 2)).intersect(Interval(1, oo)), x)
334
+ Union(Interval.Lopen(-2, -1), Interval(2, oo))
335
+ """
336
+
337
+ # TODO: handle piecewise defined functions
338
+ # TODO: handle transcendental functions
339
+ # TODO: handle multivariate functions
340
+ if len(syms) == 0:
341
+ raise ValueError("One or more symbols must be given in syms.")
342
+
343
+ if finset_intersection is S.EmptySet:
344
+ return S.EmptySet
345
+
346
+ if isinstance(finset_intersection, Union):
347
+ elm_in_sets = finset_intersection.args[0]
348
+ return Union(not_empty_in(finset_intersection.args[1], *syms),
349
+ elm_in_sets)
350
+
351
+ if isinstance(finset_intersection, FiniteSet):
352
+ finite_set = finset_intersection
353
+ _sets = S.Reals
354
+ else:
355
+ finite_set = finset_intersection.args[1]
356
+ _sets = finset_intersection.args[0]
357
+
358
+ if not isinstance(finite_set, FiniteSet):
359
+ raise ValueError('A FiniteSet must be given, not %s: %s' %
360
+ (type(finite_set), finite_set))
361
+
362
+ if len(syms) == 1:
363
+ symb = syms[0]
364
+ else:
365
+ raise NotImplementedError('more than one variables %s not handled' %
366
+ (syms,))
367
+
368
+ def elm_domain(expr, intrvl):
369
+ """ Finds the domain of an expression in any given interval """
370
+ from sympy.solvers.solveset import solveset
371
+
372
+ _start = intrvl.start
373
+ _end = intrvl.end
374
+ _singularities = solveset(expr.as_numer_denom()[1], symb,
375
+ domain=S.Reals)
376
+
377
+ if intrvl.right_open:
378
+ if _end is S.Infinity:
379
+ _domain1 = S.Reals
380
+ else:
381
+ _domain1 = solveset(expr < _end, symb, domain=S.Reals)
382
+ else:
383
+ _domain1 = solveset(expr <= _end, symb, domain=S.Reals)
384
+
385
+ if intrvl.left_open:
386
+ if _start is S.NegativeInfinity:
387
+ _domain2 = S.Reals
388
+ else:
389
+ _domain2 = solveset(expr > _start, symb, domain=S.Reals)
390
+ else:
391
+ _domain2 = solveset(expr >= _start, symb, domain=S.Reals)
392
+
393
+ # domain in the interval
394
+ expr_with_sing = Intersection(_domain1, _domain2)
395
+ expr_domain = Complement(expr_with_sing, _singularities)
396
+ return expr_domain
397
+
398
+ if isinstance(_sets, Interval):
399
+ return Union(*[elm_domain(element, _sets) for element in finite_set])
400
+
401
+ if isinstance(_sets, Union):
402
+ _domain = S.EmptySet
403
+ for intrvl in _sets.args:
404
+ _domain_element = Union(*[elm_domain(element, intrvl)
405
+ for element in finite_set])
406
+ _domain = Union(_domain, _domain_element)
407
+ return _domain
408
+
409
+
410
+ def periodicity(f, symbol, check=False):
411
+ """
412
+ Tests the given function for periodicity in the given symbol.
413
+
414
+ Parameters
415
+ ==========
416
+
417
+ f : :py:class:`~.Expr`
418
+ The concerned function.
419
+ symbol : :py:class:`~.Symbol`
420
+ The variable for which the period is to be determined.
421
+ check : bool, optional
422
+ The flag to verify whether the value being returned is a period or not.
423
+
424
+ Returns
425
+ =======
426
+
427
+ period
428
+ The period of the function is returned.
429
+ ``None`` is returned when the function is aperiodic or has a complex period.
430
+ The value of $0$ is returned as the period of a constant function.
431
+
432
+ Raises
433
+ ======
434
+
435
+ NotImplementedError
436
+ The value of the period computed cannot be verified.
437
+
438
+
439
+ Notes
440
+ =====
441
+
442
+ Currently, we do not support functions with a complex period.
443
+ The period of functions having complex periodic values such
444
+ as ``exp``, ``sinh`` is evaluated to ``None``.
445
+
446
+ The value returned might not be the "fundamental" period of the given
447
+ function i.e. it may not be the smallest periodic value of the function.
448
+
449
+ The verification of the period through the ``check`` flag is not reliable
450
+ due to internal simplification of the given expression. Hence, it is set
451
+ to ``False`` by default.
452
+
453
+ Examples
454
+ ========
455
+ >>> from sympy import periodicity, Symbol, sin, cos, tan, exp
456
+ >>> x = Symbol('x')
457
+ >>> f = sin(x) + sin(2*x) + sin(3*x)
458
+ >>> periodicity(f, x)
459
+ 2*pi
460
+ >>> periodicity(sin(x)*cos(x), x)
461
+ pi
462
+ >>> periodicity(exp(tan(2*x) - 1), x)
463
+ pi/2
464
+ >>> periodicity(sin(4*x)**cos(2*x), x)
465
+ pi
466
+ >>> periodicity(exp(x), x)
467
+ """
468
+ if symbol.kind is not NumberKind:
469
+ raise NotImplementedError("Cannot use symbol of kind %s" % symbol.kind)
470
+ temp = Dummy('x', real=True)
471
+ f = f.subs(symbol, temp)
472
+ symbol = temp
473
+
474
+ def _check(orig_f, period):
475
+ '''Return the checked period or raise an error.'''
476
+ new_f = orig_f.subs(symbol, symbol + period)
477
+ if new_f.equals(orig_f):
478
+ return period
479
+ else:
480
+ raise NotImplementedError(filldedent('''
481
+ The period of the given function cannot be verified.
482
+ When `%s` was replaced with `%s + %s` in `%s`, the result
483
+ was `%s` which was not recognized as being the same as
484
+ the original function.
485
+ So either the period was wrong or the two forms were
486
+ not recognized as being equal.
487
+ Set check=False to obtain the value.''' %
488
+ (symbol, symbol, period, orig_f, new_f)))
489
+
490
+ orig_f = f
491
+ period = None
492
+
493
+ if isinstance(f, Relational):
494
+ f = f.lhs - f.rhs
495
+
496
+ f = f.simplify()
497
+
498
+ if symbol not in f.free_symbols:
499
+ return S.Zero
500
+
501
+ if isinstance(f, TrigonometricFunction):
502
+ try:
503
+ period = f.period(symbol)
504
+ except NotImplementedError:
505
+ pass
506
+
507
+ if isinstance(f, Abs):
508
+ arg = f.args[0]
509
+ if isinstance(arg, (sec, csc, cos)):
510
+ # all but tan and cot might have a
511
+ # a period that is half as large
512
+ # so recast as sin
513
+ arg = sin(arg.args[0])
514
+ period = periodicity(arg, symbol)
515
+ if period is not None and isinstance(arg, sin):
516
+ # the argument of Abs was a trigonometric other than
517
+ # cot or tan; test to see if the half-period
518
+ # is valid. Abs(arg) has behaviour equivalent to
519
+ # orig_f, so use that for test:
520
+ orig_f = Abs(arg)
521
+ try:
522
+ return _check(orig_f, period/2)
523
+ except NotImplementedError as err:
524
+ if check:
525
+ raise NotImplementedError(err)
526
+ # else let new orig_f and period be
527
+ # checked below
528
+
529
+ if isinstance(f, exp) or (f.is_Pow and f.base == S.Exp1):
530
+ f = Pow(S.Exp1, expand_mul(f.exp))
531
+ if im(f) != 0:
532
+ period_real = periodicity(re(f), symbol)
533
+ period_imag = periodicity(im(f), symbol)
534
+ if period_real is not None and period_imag is not None:
535
+ period = lcim([period_real, period_imag])
536
+
537
+ if f.is_Pow and f.base != S.Exp1:
538
+ base, expo = f.args
539
+ base_has_sym = base.has(symbol)
540
+ expo_has_sym = expo.has(symbol)
541
+
542
+ if base_has_sym and not expo_has_sym:
543
+ period = periodicity(base, symbol)
544
+
545
+ elif expo_has_sym and not base_has_sym:
546
+ period = periodicity(expo, symbol)
547
+
548
+ else:
549
+ period = _periodicity(f.args, symbol)
550
+
551
+ elif f.is_Mul:
552
+ coeff, g = f.as_independent(symbol, as_Add=False)
553
+ if isinstance(g, TrigonometricFunction) or not equal_valued(coeff, 1):
554
+ period = periodicity(g, symbol)
555
+ else:
556
+ period = _periodicity(g.args, symbol)
557
+
558
+ elif f.is_Add:
559
+ k, g = f.as_independent(symbol)
560
+ if k is not S.Zero:
561
+ return periodicity(g, symbol)
562
+
563
+ period = _periodicity(g.args, symbol)
564
+
565
+ elif isinstance(f, Mod):
566
+ a, n = f.args
567
+
568
+ if a == symbol:
569
+ period = n
570
+ elif isinstance(a, TrigonometricFunction):
571
+ period = periodicity(a, symbol)
572
+ #check if 'f' is linear in 'symbol'
573
+ elif (a.is_polynomial(symbol) and degree(a, symbol) == 1 and
574
+ symbol not in n.free_symbols):
575
+ period = Abs(n / a.diff(symbol))
576
+
577
+ elif isinstance(f, Piecewise):
578
+ pass # not handling Piecewise yet as the return type is not favorable
579
+
580
+ elif period is None:
581
+ from sympy.solvers.decompogen import compogen, decompogen
582
+ g_s = decompogen(f, symbol)
583
+ num_of_gs = len(g_s)
584
+ if num_of_gs > 1:
585
+ for index, g in enumerate(reversed(g_s)):
586
+ start_index = num_of_gs - 1 - index
587
+ g = compogen(g_s[start_index:], symbol)
588
+ if g not in (orig_f, f): # Fix for issue 12620
589
+ period = periodicity(g, symbol)
590
+ if period is not None:
591
+ break
592
+
593
+ if period is not None:
594
+ if check:
595
+ return _check(orig_f, period)
596
+ return period
597
+
598
+ return None
599
+
600
+
601
+ def _periodicity(args, symbol):
602
+ """
603
+ Helper for `periodicity` to find the period of a list of simpler
604
+ functions.
605
+ It uses the `lcim` method to find the least common period of
606
+ all the functions.
607
+
608
+ Parameters
609
+ ==========
610
+
611
+ args : Tuple of :py:class:`~.Symbol`
612
+ All the symbols present in a function.
613
+
614
+ symbol : :py:class:`~.Symbol`
615
+ The symbol over which the function is to be evaluated.
616
+
617
+ Returns
618
+ =======
619
+
620
+ period
621
+ The least common period of the function for all the symbols
622
+ of the function.
623
+ ``None`` if for at least one of the symbols the function is aperiodic.
624
+
625
+ """
626
+ periods = []
627
+ for f in args:
628
+ period = periodicity(f, symbol)
629
+ if period is None:
630
+ return None
631
+
632
+ if period is not S.Zero:
633
+ periods.append(period)
634
+
635
+ if len(periods) > 1:
636
+ return lcim(periods)
637
+
638
+ if periods:
639
+ return periods[0]
640
+
641
+
642
+ def lcim(numbers):
643
+ """Returns the least common integral multiple of a list of numbers.
644
+
645
+ The numbers can be rational or irrational or a mixture of both.
646
+ `None` is returned for incommensurable numbers.
647
+
648
+ Parameters
649
+ ==========
650
+
651
+ numbers : list
652
+ Numbers (rational and/or irrational) for which lcim is to be found.
653
+
654
+ Returns
655
+ =======
656
+
657
+ number
658
+ lcim if it exists, otherwise ``None`` for incommensurable numbers.
659
+
660
+ Examples
661
+ ========
662
+
663
+ >>> from sympy.calculus.util import lcim
664
+ >>> from sympy import S, pi
665
+ >>> lcim([S(1)/2, S(3)/4, S(5)/6])
666
+ 15/2
667
+ >>> lcim([2*pi, 3*pi, pi, pi/2])
668
+ 6*pi
669
+ >>> lcim([S(1), 2*pi])
670
+ """
671
+ result = None
672
+ if all(num.is_irrational for num in numbers):
673
+ factorized_nums = [num.factor() for num in numbers]
674
+ factors_num = [num.as_coeff_Mul() for num in factorized_nums]
675
+ term = factors_num[0][1]
676
+ if all(factor == term for coeff, factor in factors_num):
677
+ common_term = term
678
+ coeffs = [coeff for coeff, factor in factors_num]
679
+ result = lcm_list(coeffs) * common_term
680
+
681
+ elif all(num.is_rational for num in numbers):
682
+ result = lcm_list(numbers)
683
+
684
+ else:
685
+ pass
686
+
687
+ return result
688
+
689
+ def is_convex(f, *syms, domain=S.Reals):
690
+ r"""Determines the convexity of the function passed in the argument.
691
+
692
+ Parameters
693
+ ==========
694
+
695
+ f : :py:class:`~.Expr`
696
+ The concerned function.
697
+ syms : Tuple of :py:class:`~.Symbol`
698
+ The variables with respect to which the convexity is to be determined.
699
+ domain : :py:class:`~.Interval`, optional
700
+ The domain over which the convexity of the function has to be checked.
701
+ If unspecified, S.Reals will be the default domain.
702
+
703
+ Returns
704
+ =======
705
+
706
+ bool
707
+ The method returns ``True`` if the function is convex otherwise it
708
+ returns ``False``.
709
+
710
+ Raises
711
+ ======
712
+
713
+ NotImplementedError
714
+ The check for the convexity of multivariate functions is not implemented yet.
715
+
716
+ Notes
717
+ =====
718
+
719
+ To determine concavity of a function pass `-f` as the concerned function.
720
+ To determine logarithmic convexity of a function pass `\log(f)` as
721
+ concerned function.
722
+ To determine logarithmic concavity of a function pass `-\log(f)` as
723
+ concerned function.
724
+
725
+ Currently, convexity check of multivariate functions is not handled.
726
+
727
+ Examples
728
+ ========
729
+
730
+ >>> from sympy import is_convex, symbols, exp, oo, Interval
731
+ >>> x = symbols('x')
732
+ >>> is_convex(exp(x), x)
733
+ True
734
+ >>> is_convex(x**3, x, domain = Interval(-1, oo))
735
+ False
736
+ >>> is_convex(1/x**2, x, domain=Interval.open(0, oo))
737
+ True
738
+
739
+ References
740
+ ==========
741
+
742
+ .. [1] https://en.wikipedia.org/wiki/Convex_function
743
+ .. [2] http://www.ifp.illinois.edu/~angelia/L3_convfunc.pdf
744
+ .. [3] https://en.wikipedia.org/wiki/Logarithmically_convex_function
745
+ .. [4] https://en.wikipedia.org/wiki/Logarithmically_concave_function
746
+ .. [5] https://en.wikipedia.org/wiki/Concave_function
747
+
748
+ """
749
+ if len(syms) > 1 :
750
+ return hessian(f, syms).is_positive_semidefinite
751
+ from sympy.solvers.inequalities import solve_univariate_inequality
752
+ f = _sympify(f)
753
+ var = syms[0]
754
+ if any(s in domain for s in singularities(f, var)):
755
+ return False
756
+ condition = f.diff(var, 2) < 0
757
+ if solve_univariate_inequality(condition, var, False, domain):
758
+ return False
759
+ return True
760
+
761
+
762
+ def stationary_points(f, symbol, domain=S.Reals):
763
+ """
764
+ Returns the stationary points of a function (where derivative of the
765
+ function is 0) in the given domain.
766
+
767
+ Parameters
768
+ ==========
769
+
770
+ f : :py:class:`~.Expr`
771
+ The concerned function.
772
+ symbol : :py:class:`~.Symbol`
773
+ The variable for which the stationary points are to be determined.
774
+ domain : :py:class:`~.Interval`
775
+ The domain over which the stationary points have to be checked.
776
+ If unspecified, ``S.Reals`` will be the default domain.
777
+
778
+ Returns
779
+ =======
780
+
781
+ Set
782
+ A set of stationary points for the function. If there are no
783
+ stationary point, an :py:class:`~.EmptySet` is returned.
784
+
785
+ Examples
786
+ ========
787
+
788
+ >>> from sympy import Interval, Symbol, S, sin, pi, pprint, stationary_points
789
+ >>> x = Symbol('x')
790
+
791
+ >>> stationary_points(1/x, x, S.Reals)
792
+ EmptySet
793
+
794
+ >>> pprint(stationary_points(sin(x), x), use_unicode=False)
795
+ pi 3*pi
796
+ {2*n*pi + -- | n in Integers} U {2*n*pi + ---- | n in Integers}
797
+ 2 2
798
+
799
+ >>> stationary_points(sin(x),x, Interval(0, 4*pi))
800
+ {pi/2, 3*pi/2, 5*pi/2, 7*pi/2}
801
+
802
+ """
803
+ from sympy.solvers.solveset import solveset
804
+
805
+ if domain is S.EmptySet:
806
+ return S.EmptySet
807
+
808
+ domain = continuous_domain(f, symbol, domain)
809
+ set = solveset(diff(f, symbol), symbol, domain)
810
+
811
+ return set
812
+
813
+
814
+ def maximum(f, symbol, domain=S.Reals):
815
+ """
816
+ Returns the maximum value of a function in the given domain.
817
+
818
+ Parameters
819
+ ==========
820
+
821
+ f : :py:class:`~.Expr`
822
+ The concerned function.
823
+ symbol : :py:class:`~.Symbol`
824
+ The variable for maximum value needs to be determined.
825
+ domain : :py:class:`~.Interval`
826
+ The domain over which the maximum have to be checked.
827
+ If unspecified, then the global maximum is returned.
828
+
829
+ Returns
830
+ =======
831
+
832
+ number
833
+ Maximum value of the function in given domain.
834
+
835
+ Examples
836
+ ========
837
+
838
+ >>> from sympy import Interval, Symbol, S, sin, cos, pi, maximum
839
+ >>> x = Symbol('x')
840
+
841
+ >>> f = -x**2 + 2*x + 5
842
+ >>> maximum(f, x, S.Reals)
843
+ 6
844
+
845
+ >>> maximum(sin(x), x, Interval(-pi, pi/4))
846
+ sqrt(2)/2
847
+
848
+ >>> maximum(sin(x)*cos(x), x)
849
+ 1/2
850
+
851
+ """
852
+ if isinstance(symbol, Symbol):
853
+ if domain is S.EmptySet:
854
+ raise ValueError("Maximum value not defined for empty domain.")
855
+
856
+ return function_range(f, symbol, domain).sup
857
+ else:
858
+ raise ValueError("%s is not a valid symbol." % symbol)
859
+
860
+
861
+ def minimum(f, symbol, domain=S.Reals):
862
+ """
863
+ Returns the minimum value of a function in the given domain.
864
+
865
+ Parameters
866
+ ==========
867
+
868
+ f : :py:class:`~.Expr`
869
+ The concerned function.
870
+ symbol : :py:class:`~.Symbol`
871
+ The variable for minimum value needs to be determined.
872
+ domain : :py:class:`~.Interval`
873
+ The domain over which the minimum have to be checked.
874
+ If unspecified, then the global minimum is returned.
875
+
876
+ Returns
877
+ =======
878
+
879
+ number
880
+ Minimum value of the function in the given domain.
881
+
882
+ Examples
883
+ ========
884
+
885
+ >>> from sympy import Interval, Symbol, S, sin, cos, minimum
886
+ >>> x = Symbol('x')
887
+
888
+ >>> f = x**2 + 2*x + 5
889
+ >>> minimum(f, x, S.Reals)
890
+ 4
891
+
892
+ >>> minimum(sin(x), x, Interval(2, 3))
893
+ sin(3)
894
+
895
+ >>> minimum(sin(x)*cos(x), x)
896
+ -1/2
897
+
898
+ """
899
+ if isinstance(symbol, Symbol):
900
+ if domain is S.EmptySet:
901
+ raise ValueError("Minimum value not defined for empty domain.")
902
+
903
+ return function_range(f, symbol, domain).inf
904
+ else:
905
+ raise ValueError("%s is not a valid symbol." % symbol)
lib/python3.12/site-packages/sympy/concrete/__init__.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ from .products import product, Product
2
+ from .summations import summation, Sum
3
+
4
+ __all__ = [
5
+ 'product', 'Product',
6
+
7
+ 'summation', 'Sum',
8
+ ]
lib/python3.12/site-packages/sympy/concrete/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (359 Bytes). View file
 
lib/python3.12/site-packages/sympy/concrete/__pycache__/delta.cpython-312.pyc ADDED
Binary file (13.4 kB). View file
 
lib/python3.12/site-packages/sympy/concrete/__pycache__/expr_with_intlimits.cpython-312.pyc ADDED
Binary file (13.3 kB). View file
 
lib/python3.12/site-packages/sympy/concrete/__pycache__/expr_with_limits.cpython-312.pyc ADDED
Binary file (26.9 kB). View file
 
lib/python3.12/site-packages/sympy/concrete/__pycache__/gosper.cpython-312.pyc ADDED
Binary file (8.08 kB). View file
 
lib/python3.12/site-packages/sympy/concrete/__pycache__/guess.cpython-312.pyc ADDED
Binary file (23.5 kB). View file
 
lib/python3.12/site-packages/sympy/concrete/__pycache__/products.cpython-312.pyc ADDED
Binary file (23.4 kB). View file
 
lib/python3.12/site-packages/sympy/concrete/__pycache__/summations.cpython-312.pyc ADDED
Binary file (63.7 kB). View file
 
lib/python3.12/site-packages/sympy/concrete/delta.py ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This module implements sums and products containing the Kronecker Delta function.
3
+
4
+ References
5
+ ==========
6
+
7
+ .. [1] https://mathworld.wolfram.com/KroneckerDelta.html
8
+
9
+ """
10
+ from .products import product
11
+ from .summations import Sum, summation
12
+ from sympy.core import Add, Mul, S, Dummy
13
+ from sympy.core.cache import cacheit
14
+ from sympy.core.sorting import default_sort_key
15
+ from sympy.functions import KroneckerDelta, Piecewise, piecewise_fold
16
+ from sympy.polys.polytools import factor
17
+ from sympy.sets.sets import Interval
18
+ from sympy.solvers.solvers import solve
19
+
20
+
21
+ @cacheit
22
+ def _expand_delta(expr, index):
23
+ """
24
+ Expand the first Add containing a simple KroneckerDelta.
25
+ """
26
+ if not expr.is_Mul:
27
+ return expr
28
+ delta = None
29
+ func = Add
30
+ terms = [S.One]
31
+ for h in expr.args:
32
+ if delta is None and h.is_Add and _has_simple_delta(h, index):
33
+ delta = True
34
+ func = h.func
35
+ terms = [terms[0]*t for t in h.args]
36
+ else:
37
+ terms = [t*h for t in terms]
38
+ return func(*terms)
39
+
40
+
41
+ @cacheit
42
+ def _extract_delta(expr, index):
43
+ """
44
+ Extract a simple KroneckerDelta from the expression.
45
+
46
+ Explanation
47
+ ===========
48
+
49
+ Returns the tuple ``(delta, newexpr)`` where:
50
+
51
+ - ``delta`` is a simple KroneckerDelta expression if one was found,
52
+ or ``None`` if no simple KroneckerDelta expression was found.
53
+
54
+ - ``newexpr`` is a Mul containing the remaining terms; ``expr`` is
55
+ returned unchanged if no simple KroneckerDelta expression was found.
56
+
57
+ Examples
58
+ ========
59
+
60
+ >>> from sympy import KroneckerDelta
61
+ >>> from sympy.concrete.delta import _extract_delta
62
+ >>> from sympy.abc import x, y, i, j, k
63
+ >>> _extract_delta(4*x*y*KroneckerDelta(i, j), i)
64
+ (KroneckerDelta(i, j), 4*x*y)
65
+ >>> _extract_delta(4*x*y*KroneckerDelta(i, j), k)
66
+ (None, 4*x*y*KroneckerDelta(i, j))
67
+
68
+ See Also
69
+ ========
70
+
71
+ sympy.functions.special.tensor_functions.KroneckerDelta
72
+ deltaproduct
73
+ deltasummation
74
+ """
75
+ if not _has_simple_delta(expr, index):
76
+ return (None, expr)
77
+ if isinstance(expr, KroneckerDelta):
78
+ return (expr, S.One)
79
+ if not expr.is_Mul:
80
+ raise ValueError("Incorrect expr")
81
+ delta = None
82
+ terms = []
83
+
84
+ for arg in expr.args:
85
+ if delta is None and _is_simple_delta(arg, index):
86
+ delta = arg
87
+ else:
88
+ terms.append(arg)
89
+ return (delta, expr.func(*terms))
90
+
91
+
92
+ @cacheit
93
+ def _has_simple_delta(expr, index):
94
+ """
95
+ Returns True if ``expr`` is an expression that contains a KroneckerDelta
96
+ that is simple in the index ``index``, meaning that this KroneckerDelta
97
+ is nonzero for a single value of the index ``index``.
98
+ """
99
+ if expr.has(KroneckerDelta):
100
+ if _is_simple_delta(expr, index):
101
+ return True
102
+ if expr.is_Add or expr.is_Mul:
103
+ for arg in expr.args:
104
+ if _has_simple_delta(arg, index):
105
+ return True
106
+ return False
107
+
108
+
109
+ @cacheit
110
+ def _is_simple_delta(delta, index):
111
+ """
112
+ Returns True if ``delta`` is a KroneckerDelta and is nonzero for a single
113
+ value of the index ``index``.
114
+ """
115
+ if isinstance(delta, KroneckerDelta) and delta.has(index):
116
+ p = (delta.args[0] - delta.args[1]).as_poly(index)
117
+ if p:
118
+ return p.degree() == 1
119
+ return False
120
+
121
+
122
+ @cacheit
123
+ def _remove_multiple_delta(expr):
124
+ """
125
+ Evaluate products of KroneckerDelta's.
126
+ """
127
+ if expr.is_Add:
128
+ return expr.func(*list(map(_remove_multiple_delta, expr.args)))
129
+ if not expr.is_Mul:
130
+ return expr
131
+ eqs = []
132
+ newargs = []
133
+ for arg in expr.args:
134
+ if isinstance(arg, KroneckerDelta):
135
+ eqs.append(arg.args[0] - arg.args[1])
136
+ else:
137
+ newargs.append(arg)
138
+ if not eqs:
139
+ return expr
140
+ solns = solve(eqs, dict=True)
141
+ if len(solns) == 0:
142
+ return S.Zero
143
+ elif len(solns) == 1:
144
+ for key in solns[0].keys():
145
+ newargs.append(KroneckerDelta(key, solns[0][key]))
146
+ expr2 = expr.func(*newargs)
147
+ if expr != expr2:
148
+ return _remove_multiple_delta(expr2)
149
+ return expr
150
+
151
+
152
+ @cacheit
153
+ def _simplify_delta(expr):
154
+ """
155
+ Rewrite a KroneckerDelta's indices in its simplest form.
156
+ """
157
+ if isinstance(expr, KroneckerDelta):
158
+ try:
159
+ slns = solve(expr.args[0] - expr.args[1], dict=True)
160
+ if slns and len(slns) == 1:
161
+ return Mul(*[KroneckerDelta(*(key, value))
162
+ for key, value in slns[0].items()])
163
+ except NotImplementedError:
164
+ pass
165
+ return expr
166
+
167
+
168
+ @cacheit
169
+ def deltaproduct(f, limit):
170
+ """
171
+ Handle products containing a KroneckerDelta.
172
+
173
+ See Also
174
+ ========
175
+
176
+ deltasummation
177
+ sympy.functions.special.tensor_functions.KroneckerDelta
178
+ sympy.concrete.products.product
179
+ """
180
+ if ((limit[2] - limit[1]) < 0) == True:
181
+ return S.One
182
+
183
+ if not f.has(KroneckerDelta):
184
+ return product(f, limit)
185
+
186
+ if f.is_Add:
187
+ # Identify the term in the Add that has a simple KroneckerDelta
188
+ delta = None
189
+ terms = []
190
+ for arg in sorted(f.args, key=default_sort_key):
191
+ if delta is None and _has_simple_delta(arg, limit[0]):
192
+ delta = arg
193
+ else:
194
+ terms.append(arg)
195
+ newexpr = f.func(*terms)
196
+ k = Dummy("kprime", integer=True)
197
+ if isinstance(limit[1], int) and isinstance(limit[2], int):
198
+ result = deltaproduct(newexpr, limit) + sum(deltaproduct(newexpr, (limit[0], limit[1], ik - 1)) *
199
+ delta.subs(limit[0], ik) *
200
+ deltaproduct(newexpr, (limit[0], ik + 1, limit[2])) for ik in range(int(limit[1]), int(limit[2] + 1))
201
+ )
202
+ else:
203
+ result = deltaproduct(newexpr, limit) + deltasummation(
204
+ deltaproduct(newexpr, (limit[0], limit[1], k - 1)) *
205
+ delta.subs(limit[0], k) *
206
+ deltaproduct(newexpr, (limit[0], k + 1, limit[2])),
207
+ (k, limit[1], limit[2]),
208
+ no_piecewise=_has_simple_delta(newexpr, limit[0])
209
+ )
210
+ return _remove_multiple_delta(result)
211
+
212
+ delta, _ = _extract_delta(f, limit[0])
213
+
214
+ if not delta:
215
+ g = _expand_delta(f, limit[0])
216
+ if f != g:
217
+ try:
218
+ return factor(deltaproduct(g, limit))
219
+ except AssertionError:
220
+ return deltaproduct(g, limit)
221
+ return product(f, limit)
222
+
223
+ return _remove_multiple_delta(f.subs(limit[0], limit[1])*KroneckerDelta(limit[2], limit[1])) + \
224
+ S.One*_simplify_delta(KroneckerDelta(limit[2], limit[1] - 1))
225
+
226
+
227
+ @cacheit
228
+ def deltasummation(f, limit, no_piecewise=False):
229
+ """
230
+ Handle summations containing a KroneckerDelta.
231
+
232
+ Explanation
233
+ ===========
234
+
235
+ The idea for summation is the following:
236
+
237
+ - If we are dealing with a KroneckerDelta expression, i.e. KroneckerDelta(g(x), j),
238
+ we try to simplify it.
239
+
240
+ If we could simplify it, then we sum the resulting expression.
241
+ We already know we can sum a simplified expression, because only
242
+ simple KroneckerDelta expressions are involved.
243
+
244
+ If we could not simplify it, there are two cases:
245
+
246
+ 1) The expression is a simple expression: we return the summation,
247
+ taking care if we are dealing with a Derivative or with a proper
248
+ KroneckerDelta.
249
+
250
+ 2) The expression is not simple (i.e. KroneckerDelta(cos(x))): we can do
251
+ nothing at all.
252
+
253
+ - If the expr is a multiplication expr having a KroneckerDelta term:
254
+
255
+ First we expand it.
256
+
257
+ If the expansion did work, then we try to sum the expansion.
258
+
259
+ If not, we try to extract a simple KroneckerDelta term, then we have two
260
+ cases:
261
+
262
+ 1) We have a simple KroneckerDelta term, so we return the summation.
263
+
264
+ 2) We did not have a simple term, but we do have an expression with
265
+ simplified KroneckerDelta terms, so we sum this expression.
266
+
267
+ Examples
268
+ ========
269
+
270
+ >>> from sympy import oo, symbols
271
+ >>> from sympy.abc import k
272
+ >>> i, j = symbols('i, j', integer=True, finite=True)
273
+ >>> from sympy.concrete.delta import deltasummation
274
+ >>> from sympy import KroneckerDelta
275
+ >>> deltasummation(KroneckerDelta(i, k), (k, -oo, oo))
276
+ 1
277
+ >>> deltasummation(KroneckerDelta(i, k), (k, 0, oo))
278
+ Piecewise((1, i >= 0), (0, True))
279
+ >>> deltasummation(KroneckerDelta(i, k), (k, 1, 3))
280
+ Piecewise((1, (i >= 1) & (i <= 3)), (0, True))
281
+ >>> deltasummation(k*KroneckerDelta(i, j)*KroneckerDelta(j, k), (k, -oo, oo))
282
+ j*KroneckerDelta(i, j)
283
+ >>> deltasummation(j*KroneckerDelta(i, j), (j, -oo, oo))
284
+ i
285
+ >>> deltasummation(i*KroneckerDelta(i, j), (i, -oo, oo))
286
+ j
287
+
288
+ See Also
289
+ ========
290
+
291
+ deltaproduct
292
+ sympy.functions.special.tensor_functions.KroneckerDelta
293
+ sympy.concrete.sums.summation
294
+ """
295
+ if ((limit[2] - limit[1]) < 0) == True:
296
+ return S.Zero
297
+
298
+ if not f.has(KroneckerDelta):
299
+ return summation(f, limit)
300
+
301
+ x = limit[0]
302
+
303
+ g = _expand_delta(f, x)
304
+ if g.is_Add:
305
+ return piecewise_fold(
306
+ g.func(*[deltasummation(h, limit, no_piecewise) for h in g.args]))
307
+
308
+ # try to extract a simple KroneckerDelta term
309
+ delta, expr = _extract_delta(g, x)
310
+
311
+ if (delta is not None) and (delta.delta_range is not None):
312
+ dinf, dsup = delta.delta_range
313
+ if (limit[1] - dinf <= 0) == True and (limit[2] - dsup >= 0) == True:
314
+ no_piecewise = True
315
+
316
+ if not delta:
317
+ return summation(f, limit)
318
+
319
+ solns = solve(delta.args[0] - delta.args[1], x)
320
+ if len(solns) == 0:
321
+ return S.Zero
322
+ elif len(solns) != 1:
323
+ return Sum(f, limit)
324
+ value = solns[0]
325
+ if no_piecewise:
326
+ return expr.subs(x, value)
327
+ return Piecewise(
328
+ (expr.subs(x, value), Interval(*limit[1:3]).as_relational(value)),
329
+ (S.Zero, True)
330
+ )
lib/python3.12/site-packages/sympy/concrete/expr_with_intlimits.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.concrete.expr_with_limits import ExprWithLimits
2
+ from sympy.core.singleton import S
3
+ from sympy.core.relational import Eq
4
+
5
+ class ReorderError(NotImplementedError):
6
+ """
7
+ Exception raised when trying to reorder dependent limits.
8
+ """
9
+ def __init__(self, expr, msg):
10
+ super().__init__(
11
+ "%s could not be reordered: %s." % (expr, msg))
12
+
13
+ class ExprWithIntLimits(ExprWithLimits):
14
+ """
15
+ Superclass for Product and Sum.
16
+
17
+ See Also
18
+ ========
19
+
20
+ sympy.concrete.expr_with_limits.ExprWithLimits
21
+ sympy.concrete.products.Product
22
+ sympy.concrete.summations.Sum
23
+ """
24
+ __slots__ = ()
25
+
26
+ def change_index(self, var, trafo, newvar=None):
27
+ r"""
28
+ Change index of a Sum or Product.
29
+
30
+ Perform a linear transformation `x \mapsto a x + b` on the index variable
31
+ `x`. For `a` the only values allowed are `\pm 1`. A new variable to be used
32
+ after the change of index can also be specified.
33
+
34
+ Explanation
35
+ ===========
36
+
37
+ ``change_index(expr, var, trafo, newvar=None)`` where ``var`` specifies the
38
+ index variable `x` to transform. The transformation ``trafo`` must be linear
39
+ and given in terms of ``var``. If the optional argument ``newvar`` is
40
+ provided then ``var`` gets replaced by ``newvar`` in the final expression.
41
+
42
+ Examples
43
+ ========
44
+
45
+ >>> from sympy import Sum, Product, simplify
46
+ >>> from sympy.abc import x, y, a, b, c, d, u, v, i, j, k, l
47
+
48
+ >>> S = Sum(x, (x, a, b))
49
+ >>> S.doit()
50
+ -a**2/2 + a/2 + b**2/2 + b/2
51
+
52
+ >>> Sn = S.change_index(x, x + 1, y)
53
+ >>> Sn
54
+ Sum(y - 1, (y, a + 1, b + 1))
55
+ >>> Sn.doit()
56
+ -a**2/2 + a/2 + b**2/2 + b/2
57
+
58
+ >>> Sn = S.change_index(x, -x, y)
59
+ >>> Sn
60
+ Sum(-y, (y, -b, -a))
61
+ >>> Sn.doit()
62
+ -a**2/2 + a/2 + b**2/2 + b/2
63
+
64
+ >>> Sn = S.change_index(x, x+u)
65
+ >>> Sn
66
+ Sum(-u + x, (x, a + u, b + u))
67
+ >>> Sn.doit()
68
+ -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u
69
+ >>> simplify(Sn.doit())
70
+ -a**2/2 + a/2 + b**2/2 + b/2
71
+
72
+ >>> Sn = S.change_index(x, -x - u, y)
73
+ >>> Sn
74
+ Sum(-u - y, (y, -b - u, -a - u))
75
+ >>> Sn.doit()
76
+ -a**2/2 - a*u + a/2 + b**2/2 + b*u + b/2 - u*(-a + b + 1) + u
77
+ >>> simplify(Sn.doit())
78
+ -a**2/2 + a/2 + b**2/2 + b/2
79
+
80
+ >>> P = Product(i*j**2, (i, a, b), (j, c, d))
81
+ >>> P
82
+ Product(i*j**2, (i, a, b), (j, c, d))
83
+ >>> P2 = P.change_index(i, i+3, k)
84
+ >>> P2
85
+ Product(j**2*(k - 3), (k, a + 3, b + 3), (j, c, d))
86
+ >>> P3 = P2.change_index(j, -j, l)
87
+ >>> P3
88
+ Product(l**2*(k - 3), (k, a + 3, b + 3), (l, -d, -c))
89
+
90
+ When dealing with symbols only, we can make a
91
+ general linear transformation:
92
+
93
+ >>> Sn = S.change_index(x, u*x+v, y)
94
+ >>> Sn
95
+ Sum((-v + y)/u, (y, b*u + v, a*u + v))
96
+ >>> Sn.doit()
97
+ -v*(a*u - b*u + 1)/u + (a**2*u**2/2 + a*u*v + a*u/2 - b**2*u**2/2 - b*u*v + b*u/2 + v)/u
98
+ >>> simplify(Sn.doit())
99
+ a**2*u/2 + a/2 - b**2*u/2 + b/2
100
+
101
+ However, the last result can be inconsistent with usual
102
+ summation where the index increment is always 1. This is
103
+ obvious as we get back the original value only for ``u``
104
+ equal +1 or -1.
105
+
106
+ See Also
107
+ ========
108
+
109
+ sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index,
110
+ reorder_limit,
111
+ sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder,
112
+ sympy.concrete.summations.Sum.reverse_order,
113
+ sympy.concrete.products.Product.reverse_order
114
+ """
115
+ if newvar is None:
116
+ newvar = var
117
+
118
+ limits = []
119
+ for limit in self.limits:
120
+ if limit[0] == var:
121
+ p = trafo.as_poly(var)
122
+ if p.degree() != 1:
123
+ raise ValueError("Index transformation is not linear")
124
+ alpha = p.coeff_monomial(var)
125
+ beta = p.coeff_monomial(S.One)
126
+ if alpha.is_number:
127
+ if alpha == S.One:
128
+ limits.append((newvar, alpha*limit[1] + beta, alpha*limit[2] + beta))
129
+ elif alpha == S.NegativeOne:
130
+ limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta))
131
+ else:
132
+ raise ValueError("Linear transformation results in non-linear summation stepsize")
133
+ else:
134
+ # Note that the case of alpha being symbolic can give issues if alpha < 0.
135
+ limits.append((newvar, alpha*limit[2] + beta, alpha*limit[1] + beta))
136
+ else:
137
+ limits.append(limit)
138
+
139
+ function = self.function.subs(var, (var - beta)/alpha)
140
+ function = function.subs(var, newvar)
141
+
142
+ return self.func(function, *limits)
143
+
144
+
145
+ def index(expr, x):
146
+ """
147
+ Return the index of a dummy variable in the list of limits.
148
+
149
+ Explanation
150
+ ===========
151
+
152
+ ``index(expr, x)`` returns the index of the dummy variable ``x`` in the
153
+ limits of ``expr``. Note that we start counting with 0 at the inner-most
154
+ limits tuple.
155
+
156
+ Examples
157
+ ========
158
+
159
+ >>> from sympy.abc import x, y, a, b, c, d
160
+ >>> from sympy import Sum, Product
161
+ >>> Sum(x*y, (x, a, b), (y, c, d)).index(x)
162
+ 0
163
+ >>> Sum(x*y, (x, a, b), (y, c, d)).index(y)
164
+ 1
165
+ >>> Product(x*y, (x, a, b), (y, c, d)).index(x)
166
+ 0
167
+ >>> Product(x*y, (x, a, b), (y, c, d)).index(y)
168
+ 1
169
+
170
+ See Also
171
+ ========
172
+
173
+ reorder_limit, reorder, sympy.concrete.summations.Sum.reverse_order,
174
+ sympy.concrete.products.Product.reverse_order
175
+ """
176
+ variables = [limit[0] for limit in expr.limits]
177
+
178
+ if variables.count(x) != 1:
179
+ raise ValueError(expr, "Number of instances of variable not equal to one")
180
+ else:
181
+ return variables.index(x)
182
+
183
+ def reorder(expr, *arg):
184
+ """
185
+ Reorder limits in a expression containing a Sum or a Product.
186
+
187
+ Explanation
188
+ ===========
189
+
190
+ ``expr.reorder(*arg)`` reorders the limits in the expression ``expr``
191
+ according to the list of tuples given by ``arg``. These tuples can
192
+ contain numerical indices or index variable names or involve both.
193
+
194
+ Examples
195
+ ========
196
+
197
+ >>> from sympy import Sum, Product
198
+ >>> from sympy.abc import x, y, z, a, b, c, d, e, f
199
+
200
+ >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((x, y))
201
+ Sum(x*y, (y, c, d), (x, a, b))
202
+
203
+ >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder((x, y), (x, z), (y, z))
204
+ Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b))
205
+
206
+ >>> P = Product(x*y*z, (x, a, b), (y, c, d), (z, e, f))
207
+ >>> P.reorder((x, y), (x, z), (y, z))
208
+ Product(x*y*z, (z, e, f), (y, c, d), (x, a, b))
209
+
210
+ We can also select the index variables by counting them, starting
211
+ with the inner-most one:
212
+
213
+ >>> Sum(x**2, (x, a, b), (x, c, d)).reorder((0, 1))
214
+ Sum(x**2, (x, c, d), (x, a, b))
215
+
216
+ And of course we can mix both schemes:
217
+
218
+ >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, x))
219
+ Sum(x*y, (y, c, d), (x, a, b))
220
+ >>> Sum(x*y, (x, a, b), (y, c, d)).reorder((y, 0))
221
+ Sum(x*y, (y, c, d), (x, a, b))
222
+
223
+ See Also
224
+ ========
225
+
226
+ reorder_limit, index, sympy.concrete.summations.Sum.reverse_order,
227
+ sympy.concrete.products.Product.reverse_order
228
+ """
229
+ new_expr = expr
230
+
231
+ for r in arg:
232
+ if len(r) != 2:
233
+ raise ValueError(r, "Invalid number of arguments")
234
+
235
+ index1 = r[0]
236
+ index2 = r[1]
237
+
238
+ if not isinstance(r[0], int):
239
+ index1 = expr.index(r[0])
240
+ if not isinstance(r[1], int):
241
+ index2 = expr.index(r[1])
242
+
243
+ new_expr = new_expr.reorder_limit(index1, index2)
244
+
245
+ return new_expr
246
+
247
+
248
+ def reorder_limit(expr, x, y):
249
+ """
250
+ Interchange two limit tuples of a Sum or Product expression.
251
+
252
+ Explanation
253
+ ===========
254
+
255
+ ``expr.reorder_limit(x, y)`` interchanges two limit tuples. The
256
+ arguments ``x`` and ``y`` are integers corresponding to the index
257
+ variables of the two limits which are to be interchanged. The
258
+ expression ``expr`` has to be either a Sum or a Product.
259
+
260
+ Examples
261
+ ========
262
+
263
+ >>> from sympy.abc import x, y, z, a, b, c, d, e, f
264
+ >>> from sympy import Sum, Product
265
+
266
+ >>> Sum(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2)
267
+ Sum(x*y*z, (z, e, f), (y, c, d), (x, a, b))
268
+ >>> Sum(x**2, (x, a, b), (x, c, d)).reorder_limit(1, 0)
269
+ Sum(x**2, (x, c, d), (x, a, b))
270
+
271
+ >>> Product(x*y*z, (x, a, b), (y, c, d), (z, e, f)).reorder_limit(0, 2)
272
+ Product(x*y*z, (z, e, f), (y, c, d), (x, a, b))
273
+
274
+ See Also
275
+ ========
276
+
277
+ index, reorder, sympy.concrete.summations.Sum.reverse_order,
278
+ sympy.concrete.products.Product.reverse_order
279
+ """
280
+ var = {limit[0] for limit in expr.limits}
281
+ limit_x = expr.limits[x]
282
+ limit_y = expr.limits[y]
283
+
284
+ if (len(set(limit_x[1].free_symbols).intersection(var)) == 0 and
285
+ len(set(limit_x[2].free_symbols).intersection(var)) == 0 and
286
+ len(set(limit_y[1].free_symbols).intersection(var)) == 0 and
287
+ len(set(limit_y[2].free_symbols).intersection(var)) == 0):
288
+
289
+ limits = []
290
+ for i, limit in enumerate(expr.limits):
291
+ if i == x:
292
+ limits.append(limit_y)
293
+ elif i == y:
294
+ limits.append(limit_x)
295
+ else:
296
+ limits.append(limit)
297
+
298
+ return type(expr)(expr.function, *limits)
299
+ else:
300
+ raise ReorderError(expr, "could not interchange the two limits specified")
301
+
302
+ @property
303
+ def has_empty_sequence(self):
304
+ """
305
+ Returns True if the Sum or Product is computed for an empty sequence.
306
+
307
+ Examples
308
+ ========
309
+
310
+ >>> from sympy import Sum, Product, Symbol
311
+ >>> m = Symbol('m')
312
+ >>> Sum(m, (m, 1, 0)).has_empty_sequence
313
+ True
314
+
315
+ >>> Sum(m, (m, 1, 1)).has_empty_sequence
316
+ False
317
+
318
+ >>> M = Symbol('M', integer=True, positive=True)
319
+ >>> Product(m, (m, 1, M)).has_empty_sequence
320
+ False
321
+
322
+ >>> Product(m, (m, 2, M)).has_empty_sequence
323
+
324
+ >>> Product(m, (m, M + 1, M)).has_empty_sequence
325
+ True
326
+
327
+ >>> N = Symbol('N', integer=True, positive=True)
328
+ >>> Sum(m, (m, N, M)).has_empty_sequence
329
+
330
+ >>> N = Symbol('N', integer=True, negative=True)
331
+ >>> Sum(m, (m, N, M)).has_empty_sequence
332
+ False
333
+
334
+ See Also
335
+ ========
336
+
337
+ has_reversed_limits
338
+ has_finite_limits
339
+
340
+ """
341
+ ret_None = False
342
+ for lim in self.limits:
343
+ dif = lim[1] - lim[2]
344
+ eq = Eq(dif, 1)
345
+ if eq == True:
346
+ return True
347
+ elif eq == False:
348
+ continue
349
+ else:
350
+ ret_None = True
351
+
352
+ if ret_None:
353
+ return None
354
+ return False
lib/python3.12/site-packages/sympy/concrete/expr_with_limits.py ADDED
@@ -0,0 +1,603 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sympy.core.add import Add
2
+ from sympy.core.containers import Tuple
3
+ from sympy.core.expr import Expr
4
+ from sympy.core.function import AppliedUndef, UndefinedFunction
5
+ from sympy.core.mul import Mul
6
+ from sympy.core.relational import Equality, Relational
7
+ from sympy.core.singleton import S
8
+ from sympy.core.symbol import Symbol, Dummy
9
+ from sympy.core.sympify import sympify
10
+ from sympy.functions.elementary.piecewise import (piecewise_fold,
11
+ Piecewise)
12
+ from sympy.logic.boolalg import BooleanFunction
13
+ from sympy.matrices.matrixbase import MatrixBase
14
+ from sympy.sets.sets import Interval, Set
15
+ from sympy.sets.fancysets import Range
16
+ from sympy.tensor.indexed import Idx
17
+ from sympy.utilities import flatten
18
+ from sympy.utilities.iterables import sift, is_sequence
19
+ from sympy.utilities.exceptions import sympy_deprecation_warning
20
+
21
+
22
+ def _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)
27
+
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.
37
+
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
+ )
47
+
48
+ lhs = function.lhs
49
+ rhs = function.rhs
50
+ return Equality(cls(lhs, *symbols, **assumptions), \
51
+ cls(rhs, *symbols, **assumptions))
52
+
53
+ if function is S.NaN:
54
+ return S.NaN
55
+
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
69
+
70
+ # denest any nested calls
71
+ while cls == type(function):
72
+ limits = list(function.limits) + limits
73
+ function = function.function
74
+
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()})
89
+
90
+ return function, limits, orientation
91
+
92
+
93
+ def _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]
160
+
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
193
+
194
+ raise ValueError('Invalid limits given: %s' % str(symbols))
195
+
196
+ return limits, orientation
197
+
198
+
199
+ class ExprWithLimits(Expr):
200
+ __slots__ = ('is_commutative',)
201
+
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
210
+
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.')
215
+
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
221
+
222
+ return obj
223
+
224
+ @property
225
+ def function(self):
226
+ """Return the function applied across limits.
227
+
228
+ Examples
229
+ ========
230
+
231
+ >>> from sympy import Integral
232
+ >>> from sympy.abc import x
233
+ >>> Integral(x**2, (x,)).function
234
+ x**2
235
+
236
+ See Also
237
+ ========
238
+
239
+ limits, variables, free_symbols
240
+ """
241
+ return self._args[0]
242
+
243
+ @property
244
+ def kind(self):
245
+ return self.function.kind
246
+
247
+ @property
248
+ def limits(self):
249
+ """Return the limits of expression.
250
+
251
+ Examples
252
+ ========
253
+
254
+ >>> from sympy import Integral
255
+ >>> from sympy.abc import x, i
256
+ >>> Integral(x**i, (i, 1, 3)).limits
257
+ ((i, 1, 3),)
258
+
259
+ See Also
260
+ ========
261
+
262
+ function, variables, free_symbols
263
+ """
264
+ return self._args[1:]
265
+
266
+ @property
267
+ def variables(self):
268
+ """Return a list of the limit variables.
269
+
270
+ >>> from sympy import Sum
271
+ >>> from sympy.abc import x, i
272
+ >>> Sum(x**i, (i, 1, 3)).variables
273
+ [i]
274
+
275
+ See Also
276
+ ========
277
+
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]
283
+
284
+ @property
285
+ def bound_symbols(self):
286
+ """Return only variables that are dummy variables.
287
+
288
+ Examples
289
+ ========
290
+
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]
295
+
296
+ See Also
297
+ ========
298
+
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]
304
+
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).
310
+
311
+ Examples
312
+ ========
313
+
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}
342
+
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
347
+
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)
352
+
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.
358
+
359
+ Examples
360
+ ========
361
+
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))
366
+
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))
371
+
372
+ See Also
373
+ ========
374
+
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
378
+
379
+ """
380
+ func, limits = self.function, list(self.limits)
381
+
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.
389
+
390
+ # Reorder limits to match standard mathematical practice for scoping
391
+ limits.reverse()
392
+
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], )
426
+
427
+ # Reorder limits back to representation-form
428
+ limits.reverse()
429
+
430
+ return self.func(func, *limits)
431
+
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.
439
+
440
+ Examples
441
+ ========
442
+
443
+ >>> from sympy import Sum, Integral, Product, oo, Symbol
444
+ >>> x = Symbol('x')
445
+ >>> Sum(x, (x, 1, 8)).has_finite_limits
446
+ True
447
+
448
+ >>> Integral(x, (x, 1, oo)).has_finite_limits
449
+ False
450
+
451
+ >>> M = Symbol('M')
452
+ >>> Sum(x, (x, 1, M)).has_finite_limits
453
+
454
+ >>> N = Symbol('N', integer=True)
455
+ >>> Product(x, (x, 1, N)).has_finite_limits
456
+ True
457
+
458
+ See Also
459
+ ========
460
+
461
+ has_reversed_limits
462
+
463
+ """
464
+
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
478
+
479
+ if ret_None:
480
+ return None
481
+ return True
482
+
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.
490
+
491
+ Examples
492
+ ========
493
+
494
+ >>> from sympy import Sum, Integral, Product, oo, Symbol
495
+ >>> x = Symbol('x')
496
+ >>> Sum(x, (x, 8, 1)).has_reversed_limits
497
+ True
498
+
499
+ >>> Sum(x, (x, 1, oo)).has_reversed_limits
500
+ False
501
+
502
+ >>> M = Symbol('M')
503
+ >>> Integral(x, (x, 1, M)).has_reversed_limits
504
+
505
+ >>> N = Symbol('N', integer=True, positive=True)
506
+ >>> Sum(x, (x, 1, N)).has_reversed_limits
507
+ False
508
+
509
+ >>> Product(x, (x, 2, N)).has_reversed_limits
510
+
511
+ >>> Product(x, (x, 2, N)).subs(N, N + 2).has_reversed_limits
512
+ False
513
+
514
+ See Also
515
+ ========
516
+
517
+ sympy.concrete.expr_with_intlimits.ExprWithIntLimits.has_empty_sequence
518
+
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
536
+
537
+
538
+ class AddWithLimits(ExprWithLimits):
539
+ r"""Represents unevaluated oriented additions.
540
+ Parent class for Integral and Sum.
541
+ """
542
+
543
+ __slots__ = ()
544
+
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
553
+
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
559
+
560
+ return obj
561
+
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
566
+
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
571
+
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
576
+
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
592
+
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
lib/python3.12/site-packages/sympy/concrete/gosper.py ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Gosper's algorithm for hypergeometric summation. """
2
+
3
+ from sympy.core import S, Dummy, symbols
4
+ from sympy.polys import Poly, parallel_poly_from_expr, factor
5
+ from sympy.utilities.iterables import is_sequence
6
+
7
+
8
+ def gosper_normal(f, g, n, polys=True):
9
+ r"""
10
+ Compute the Gosper's normal form of ``f`` and ``g``.
11
+
12
+ Explanation
13
+ ===========
14
+
15
+ Given relatively prime univariate polynomials ``f`` and ``g``,
16
+ rewrite their quotient to a normal form defined as follows:
17
+
18
+ .. math::
19
+ \frac{f(n)}{g(n)} = Z \cdot \frac{A(n) C(n+1)}{B(n) C(n)}
20
+
21
+ where ``Z`` is an arbitrary constant and ``A``, ``B``, ``C`` are
22
+ monic polynomials in ``n`` with the following properties:
23
+
24
+ 1. `\gcd(A(n), B(n+h)) = 1 \forall h \in \mathbb{N}`
25
+ 2. `\gcd(B(n), C(n+1)) = 1`
26
+ 3. `\gcd(A(n), C(n)) = 1`
27
+
28
+ This normal form, or rational factorization in other words, is a
29
+ crucial step in Gosper's algorithm and in solving of difference
30
+ equations. It can be also used to decide if two hypergeometric
31
+ terms are similar or not.
32
+
33
+ This procedure will return a tuple containing elements of this
34
+ factorization in the form ``(Z*A, B, C)``.
35
+
36
+ Examples
37
+ ========
38
+
39
+ >>> from sympy.concrete.gosper import gosper_normal
40
+ >>> from sympy.abc import n
41
+
42
+ >>> gosper_normal(4*n+5, 2*(4*n+1)*(2*n+3), n, polys=False)
43
+ (1/4, n + 3/2, n + 1/4)
44
+
45
+ """
46
+ (p, q), opt = parallel_poly_from_expr(
47
+ (f, g), n, field=True, extension=True)
48
+
49
+ a, A = p.LC(), p.monic()
50
+ b, B = q.LC(), q.monic()
51
+
52
+ C, Z = A.one, a/b
53
+ h = Dummy('h')
54
+
55
+ D = Poly(n + h, n, h, domain=opt.domain)
56
+
57
+ R = A.resultant(B.compose(D))
58
+ roots = set(R.ground_roots().keys())
59
+
60
+ for r in set(roots):
61
+ if not r.is_Integer or r < 0:
62
+ roots.remove(r)
63
+
64
+ for i in sorted(roots):
65
+ d = A.gcd(B.shift(+i))
66
+
67
+ A = A.quo(d)
68
+ B = B.quo(d.shift(-i))
69
+
70
+ for j in range(1, i + 1):
71
+ C *= d.shift(-j)
72
+
73
+ A = A.mul_ground(Z)
74
+
75
+ if not polys:
76
+ A = A.as_expr()
77
+ B = B.as_expr()
78
+ C = C.as_expr()
79
+
80
+ return A, B, C
81
+
82
+
83
+ def gosper_term(f, n):
84
+ r"""
85
+ Compute Gosper's hypergeometric term for ``f``.
86
+
87
+ Explanation
88
+ ===========
89
+
90
+ Suppose ``f`` is a hypergeometric term such that:
91
+
92
+ .. math::
93
+ s_n = \sum_{k=0}^{n-1} f_k
94
+
95
+ and `f_k` does not depend on `n`. Returns a hypergeometric
96
+ term `g_n` such that `g_{n+1} - g_n = f_n`.
97
+
98
+ Examples
99
+ ========
100
+
101
+ >>> from sympy.concrete.gosper import gosper_term
102
+ >>> from sympy import factorial
103
+ >>> from sympy.abc import n
104
+
105
+ >>> gosper_term((4*n + 1)*factorial(n)/factorial(2*n + 1), n)
106
+ (-n - 1/2)/(n + 1/4)
107
+
108
+ """
109
+ from sympy.simplify import hypersimp
110
+ r = hypersimp(f, n)
111
+
112
+ if r is None:
113
+ return None # 'f' is *not* a hypergeometric term
114
+
115
+ p, q = r.as_numer_denom()
116
+
117
+ A, B, C = gosper_normal(p, q, n)
118
+ B = B.shift(-1)
119
+
120
+ N = S(A.degree())
121
+ M = S(B.degree())
122
+ K = S(C.degree())
123
+
124
+ if (N != M) or (A.LC() != B.LC()):
125
+ D = {K - max(N, M)}
126
+ elif not N:
127
+ D = {K - N + 1, S.Zero}
128
+ else:
129
+ D = {K - N + 1, (B.nth(N - 1) - A.nth(N - 1))/A.LC()}
130
+
131
+ for d in set(D):
132
+ if not d.is_Integer or d < 0:
133
+ D.remove(d)
134
+
135
+ if not D:
136
+ return None # 'f(n)' is *not* Gosper-summable
137
+
138
+ d = max(D)
139
+
140
+ coeffs = symbols('c:%s' % (d + 1), cls=Dummy)
141
+ domain = A.get_domain().inject(*coeffs)
142
+
143
+ x = Poly(coeffs, n, domain=domain)
144
+ H = A*x.shift(1) - B*x - C
145
+
146
+ from sympy.solvers.solvers import solve
147
+ solution = solve(H.coeffs(), coeffs)
148
+
149
+ if solution is None:
150
+ return None # 'f(n)' is *not* Gosper-summable
151
+
152
+ x = x.as_expr().subs(solution)
153
+
154
+ for coeff in coeffs:
155
+ if coeff not in solution:
156
+ x = x.subs(coeff, 0)
157
+
158
+ if x.is_zero:
159
+ return None # 'f(n)' is *not* Gosper-summable
160
+ else:
161
+ return B.as_expr()*x/C.as_expr()
162
+
163
+
164
+ def gosper_sum(f, k):
165
+ r"""
166
+ Gosper's hypergeometric summation algorithm.
167
+
168
+ Explanation
169
+ ===========
170
+
171
+ Given a hypergeometric term ``f`` such that:
172
+
173
+ .. math ::
174
+ s_n = \sum_{k=0}^{n-1} f_k
175
+
176
+ and `f(n)` does not depend on `n`, returns `g_{n} - g(0)` where
177
+ `g_{n+1} - g_n = f_n`, or ``None`` if `s_n` cannot be expressed
178
+ in closed form as a sum of hypergeometric terms.
179
+
180
+ Examples
181
+ ========
182
+
183
+ >>> from sympy.concrete.gosper import gosper_sum
184
+ >>> from sympy import factorial
185
+ >>> from sympy.abc import n, k
186
+
187
+ >>> f = (4*k + 1)*factorial(k)/factorial(2*k + 1)
188
+ >>> gosper_sum(f, (k, 0, n))
189
+ (-factorial(n) + 2*factorial(2*n + 1))/factorial(2*n + 1)
190
+ >>> _.subs(n, 2) == sum(f.subs(k, i) for i in [0, 1, 2])
191
+ True
192
+ >>> gosper_sum(f, (k, 3, n))
193
+ (-60*factorial(n) + factorial(2*n + 1))/(60*factorial(2*n + 1))
194
+ >>> _.subs(n, 5) == sum(f.subs(k, i) for i in [3, 4, 5])
195
+ True
196
+
197
+ References
198
+ ==========
199
+
200
+ .. [1] Marko Petkovsek, Herbert S. Wilf, Doron Zeilberger, A = B,
201
+ AK Peters, Ltd., Wellesley, MA, USA, 1997, pp. 73--100
202
+
203
+ """
204
+ indefinite = False
205
+
206
+ if is_sequence(k):
207
+ k, a, b = k
208
+ else:
209
+ indefinite = True
210
+
211
+ g = gosper_term(f, k)
212
+
213
+ if g is None:
214
+ return None
215
+
216
+ if indefinite:
217
+ result = f*g
218
+ else:
219
+ result = (f*(g + 1)).subs(k, b) - (f*g).subs(k, a)
220
+
221
+ if result is S.NaN:
222
+ try:
223
+ result = (f*(g + 1)).limit(k, b) - (f*g).limit(k, a)
224
+ except NotImplementedError:
225
+ result = None
226
+
227
+ return factor(result)
lib/python3.12/site-packages/sympy/concrete/guess.py ADDED
@@ -0,0 +1,473 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Various algorithms for helping identifying numbers and sequences."""
2
+
3
+
4
+ from sympy.concrete.products import (Product, product)
5
+ from sympy.core import Function, S
6
+ from sympy.core.add import Add
7
+ from sympy.core.numbers import Integer, Rational
8
+ from sympy.core.symbol import Symbol, symbols
9
+ from sympy.core.sympify import sympify
10
+ from sympy.functions.elementary.exponential import exp
11
+ from sympy.functions.elementary.integers import floor
12
+ from sympy.integrals.integrals import integrate
13
+ from sympy.polys.polyfuncs import rational_interpolate as rinterp
14
+ from sympy.polys.polytools import lcm
15
+ from sympy.simplify.radsimp import denom
16
+ from sympy.utilities import public
17
+
18
+
19
+ @public
20
+ def find_simple_recurrence_vector(l):
21
+ """
22
+ This function is used internally by other functions from the
23
+ sympy.concrete.guess module. While most users may want to rather use the
24
+ function find_simple_recurrence when looking for recurrence relations
25
+ among rational numbers, the current function may still be useful when
26
+ some post-processing has to be done.
27
+
28
+ Explanation
29
+ ===========
30
+
31
+ The function returns a vector of length n when a recurrence relation of
32
+ order n is detected in the sequence of rational numbers v.
33
+
34
+ If the returned vector has a length 1, then the returned value is always
35
+ the list [0], which means that no relation has been found.
36
+
37
+ While the functions is intended to be used with rational numbers, it should
38
+ work for other kinds of real numbers except for some cases involving
39
+ quadratic numbers; for that reason it should be used with some caution when
40
+ the argument is not a list of rational numbers.
41
+
42
+ Examples
43
+ ========
44
+
45
+ >>> from sympy.concrete.guess import find_simple_recurrence_vector
46
+ >>> from sympy import fibonacci
47
+ >>> find_simple_recurrence_vector([fibonacci(k) for k in range(12)])
48
+ [1, -1, -1]
49
+
50
+ See Also
51
+ ========
52
+
53
+ See the function sympy.concrete.guess.find_simple_recurrence which is more
54
+ user-friendly.
55
+
56
+ """
57
+ q1 = [0]
58
+ q2 = [1]
59
+ b, z = 0, len(l) >> 1
60
+ while len(q2) <= z:
61
+ while l[b]==0:
62
+ b += 1
63
+ if b == len(l):
64
+ c = 1
65
+ for x in q2:
66
+ c = lcm(c, denom(x))
67
+ if q2[0]*c < 0: c = -c
68
+ for k in range(len(q2)):
69
+ q2[k] = int(q2[k]*c)
70
+ return q2
71
+ a = S.One/l[b]
72
+ m = [a]
73
+ for k in range(b+1, len(l)):
74
+ m.append(-sum(l[j+1]*m[b-j-1] for j in range(b, k))*a)
75
+ l, m = m, [0] * max(len(q2), b+len(q1))
76
+ for k, q in enumerate(q2):
77
+ m[k] = a*q
78
+ for k, q in enumerate(q1):
79
+ m[k+b] += q
80
+ while m[-1]==0: m.pop() # because trailing zeros can occur
81
+ q1, q2, b = q2, m, 1
82
+ return [0]
83
+
84
+ @public
85
+ def find_simple_recurrence(v, A=Function('a'), N=Symbol('n')):
86
+ """
87
+ Detects and returns a recurrence relation from a sequence of several integer
88
+ (or rational) terms. The name of the function in the returned expression is
89
+ 'a' by default; the main variable is 'n' by default. The smallest index in
90
+ the returned expression is always n (and never n-1, n-2, etc.).
91
+
92
+ Examples
93
+ ========
94
+
95
+ >>> from sympy.concrete.guess import find_simple_recurrence
96
+ >>> from sympy import fibonacci
97
+ >>> find_simple_recurrence([fibonacci(k) for k in range(12)])
98
+ -a(n) - a(n + 1) + a(n + 2)
99
+
100
+ >>> from sympy import Function, Symbol
101
+ >>> a = [1, 1, 1]
102
+ >>> for k in range(15): a.append(5*a[-1]-3*a[-2]+8*a[-3])
103
+ >>> find_simple_recurrence(a, A=Function('f'), N=Symbol('i'))
104
+ -8*f(i) + 3*f(i + 1) - 5*f(i + 2) + f(i + 3)
105
+
106
+ """
107
+ p = find_simple_recurrence_vector(v)
108
+ n = len(p)
109
+ if n <= 1: return S.Zero
110
+
111
+ return Add(*[A(N+n-1-k)*p[k] for k in range(n)])
112
+
113
+
114
+ @public
115
+ def rationalize(x, maxcoeff=10000):
116
+ """
117
+ Helps identifying a rational number from a float (or mpmath.mpf) value by
118
+ using a continued fraction. The algorithm stops as soon as a large partial
119
+ quotient is detected (greater than 10000 by default).
120
+
121
+ Examples
122
+ ========
123
+
124
+ >>> from sympy.concrete.guess import rationalize
125
+ >>> from mpmath import cos, pi
126
+ >>> rationalize(cos(pi/3))
127
+ 1/2
128
+
129
+ >>> from mpmath import mpf
130
+ >>> rationalize(mpf("0.333333333333333"))
131
+ 1/3
132
+
133
+ While the function is rather intended to help 'identifying' rational
134
+ values, it may be used in some cases for approximating real numbers.
135
+ (Though other functions may be more relevant in that case.)
136
+
137
+ >>> rationalize(pi, maxcoeff = 250)
138
+ 355/113
139
+
140
+ See Also
141
+ ========
142
+
143
+ Several other methods can approximate a real number as a rational, like:
144
+
145
+ * fractions.Fraction.from_decimal
146
+ * fractions.Fraction.from_float
147
+ * mpmath.identify
148
+ * mpmath.pslq by using the following syntax: mpmath.pslq([x, 1])
149
+ * mpmath.findpoly by using the following syntax: mpmath.findpoly(x, 1)
150
+ * sympy.simplify.nsimplify (which is a more general function)
151
+
152
+ The main difference between the current function and all these variants is
153
+ that control focuses on magnitude of partial quotients here rather than on
154
+ global precision of the approximation. If the real is "known to be" a
155
+ rational number, the current function should be able to detect it correctly
156
+ with the default settings even when denominator is great (unless its
157
+ expansion contains unusually big partial quotients) which may occur
158
+ when studying sequences of increasing numbers. If the user cares more
159
+ on getting simple fractions, other methods may be more convenient.
160
+
161
+ """
162
+ p0, p1 = 0, 1
163
+ q0, q1 = 1, 0
164
+ a = floor(x)
165
+ while a < maxcoeff or q1==0:
166
+ p = a*p1 + p0
167
+ q = a*q1 + q0
168
+ p0, p1 = p1, p
169
+ q0, q1 = q1, q
170
+ if x==a: break
171
+ x = 1/(x-a)
172
+ a = floor(x)
173
+ return sympify(p) / q
174
+
175
+
176
+ @public
177
+ def guess_generating_function_rational(v, X=Symbol('x')):
178
+ """
179
+ Tries to "guess" a rational generating function for a sequence of rational
180
+ numbers v.
181
+
182
+ Examples
183
+ ========
184
+
185
+ >>> from sympy.concrete.guess import guess_generating_function_rational
186
+ >>> from sympy import fibonacci
187
+ >>> l = [fibonacci(k) for k in range(5,15)]
188
+ >>> guess_generating_function_rational(l)
189
+ (3*x + 5)/(-x**2 - x + 1)
190
+
191
+ See Also
192
+ ========
193
+
194
+ sympy.series.approximants
195
+ mpmath.pade
196
+
197
+ """
198
+ # a) compute the denominator as q
199
+ q = find_simple_recurrence_vector(v)
200
+ n = len(q)
201
+ if n <= 1: return None
202
+ # b) compute the numerator as p
203
+ p = [sum(v[i-k]*q[k] for k in range(min(i+1, n)))
204
+ for i in range(len(v)>>1)]
205
+ return (sum(p[k]*X**k for k in range(len(p)))
206
+ / sum(q[k]*X**k for k in range(n)))
207
+
208
+
209
+ @public
210
+ def guess_generating_function(v, X=Symbol('x'), types=['all'], maxsqrtn=2):
211
+ """
212
+ Tries to "guess" a generating function for a sequence of rational numbers v.
213
+ Only a few patterns are implemented yet.
214
+
215
+ Explanation
216
+ ===========
217
+
218
+ The function returns a dictionary where keys are the name of a given type of
219
+ generating function. Six types are currently implemented:
220
+
221
+ type | formal definition
222
+ -------+----------------------------------------------------------------
223
+ ogf | f(x) = Sum( a_k * x^k , k: 0..infinity )
224
+ egf | f(x) = Sum( a_k * x^k / k! , k: 0..infinity )
225
+ lgf | f(x) = Sum( (-1)^(k+1) a_k * x^k / k , k: 1..infinity )
226
+ | (with initial index being hold as 1 rather than 0)
227
+ hlgf | f(x) = Sum( a_k * x^k / k , k: 1..infinity )
228
+ | (with initial index being hold as 1 rather than 0)
229
+ lgdogf | f(x) = derivate( log(Sum( a_k * x^k, k: 0..infinity )), x)
230
+ lgdegf | f(x) = derivate( log(Sum( a_k * x^k / k!, k: 0..infinity )), x)
231
+
232
+ In order to spare time, the user can select only some types of generating
233
+ functions (default being ['all']). While forgetting to use a list in the
234
+ case of a single type may seem to work most of the time as in: types='ogf'
235
+ this (convenient) syntax may lead to unexpected extra results in some cases.
236
+
237
+ Discarding a type when calling the function does not mean that the type will
238
+ not be present in the returned dictionary; it only means that no extra
239
+ computation will be performed for that type, but the function may still add
240
+ it in the result when it can be easily converted from another type.
241
+
242
+ Two generating functions (lgdogf and lgdegf) are not even computed if the
243
+ initial term of the sequence is 0; it may be useful in that case to try
244
+ again after having removed the leading zeros.
245
+
246
+ Examples
247
+ ========
248
+
249
+ >>> from sympy.concrete.guess import guess_generating_function as ggf
250
+ >>> ggf([k+1 for k in range(12)], types=['ogf', 'lgf', 'hlgf'])
251
+ {'hlgf': 1/(1 - x), 'lgf': 1/(x + 1), 'ogf': 1/(x**2 - 2*x + 1)}
252
+
253
+ >>> from sympy import sympify
254
+ >>> l = sympify("[3/2, 11/2, 0, -121/2, -363/2, 121]")
255
+ >>> ggf(l)
256
+ {'ogf': (x + 3/2)/(11*x**2 - 3*x + 1)}
257
+
258
+ >>> from sympy import fibonacci
259
+ >>> ggf([fibonacci(k) for k in range(5, 15)], types=['ogf'])
260
+ {'ogf': (3*x + 5)/(-x**2 - x + 1)}
261
+
262
+ >>> from sympy import factorial
263
+ >>> ggf([factorial(k) for k in range(12)], types=['ogf', 'egf', 'lgf'])
264
+ {'egf': 1/(1 - x)}
265
+
266
+ >>> ggf([k+1 for k in range(12)], types=['egf'])
267
+ {'egf': (x + 1)*exp(x), 'lgdegf': (x + 2)/(x + 1)}
268
+
269
+ N-th root of a rational function can also be detected (below is an example
270
+ coming from the sequence A108626 from https://oeis.org).
271
+ The greatest n-th root to be tested is specified as maxsqrtn (default 2).
272
+
273
+ >>> ggf([1, 2, 5, 14, 41, 124, 383, 1200, 3799, 12122, 38919])['ogf']
274
+ sqrt(1/(x**4 + 2*x**2 - 4*x + 1))
275
+
276
+ References
277
+ ==========
278
+
279
+ .. [1] "Concrete Mathematics", R.L. Graham, D.E. Knuth, O. Patashnik
280
+ .. [2] https://oeis.org/wiki/Generating_functions
281
+
282
+ """
283
+ # List of all types of all g.f. known by the algorithm
284
+ if 'all' in types:
285
+ types = ('ogf', 'egf', 'lgf', 'hlgf', 'lgdogf', 'lgdegf')
286
+
287
+ result = {}
288
+
289
+ # Ordinary Generating Function (ogf)
290
+ if 'ogf' in types:
291
+ # Perform some convolutions of the sequence with itself
292
+ t = [1] + [0]*(len(v) - 1)
293
+ for d in range(max(1, maxsqrtn)):
294
+ t = [sum(t[n-i]*v[i] for i in range(n+1)) for n in range(len(v))]
295
+ g = guess_generating_function_rational(t, X=X)
296
+ if g:
297
+ result['ogf'] = g**Rational(1, d+1)
298
+ break
299
+
300
+ # Exponential Generating Function (egf)
301
+ if 'egf' in types:
302
+ # Transform sequence (division by factorial)
303
+ w, f = [], S.One
304
+ for i, k in enumerate(v):
305
+ f *= i if i else 1
306
+ w.append(k/f)
307
+ # Perform some convolutions of the sequence with itself
308
+ t = [1] + [0]*(len(w) - 1)
309
+ for d in range(max(1, maxsqrtn)):
310
+ t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
311
+ g = guess_generating_function_rational(t, X=X)
312
+ if g:
313
+ result['egf'] = g**Rational(1, d+1)
314
+ break
315
+
316
+ # Logarithmic Generating Function (lgf)
317
+ if 'lgf' in types:
318
+ # Transform sequence (multiplication by (-1)^(n+1) / n)
319
+ w, f = [], S.NegativeOne
320
+ for i, k in enumerate(v):
321
+ f = -f
322
+ w.append(f*k/Integer(i+1))
323
+ # Perform some convolutions of the sequence with itself
324
+ t = [1] + [0]*(len(w) - 1)
325
+ for d in range(max(1, maxsqrtn)):
326
+ t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
327
+ g = guess_generating_function_rational(t, X=X)
328
+ if g:
329
+ result['lgf'] = g**Rational(1, d+1)
330
+ break
331
+
332
+ # Hyperbolic logarithmic Generating Function (hlgf)
333
+ if 'hlgf' in types:
334
+ # Transform sequence (division by n+1)
335
+ w = []
336
+ for i, k in enumerate(v):
337
+ w.append(k/Integer(i+1))
338
+ # Perform some convolutions of the sequence with itself
339
+ t = [1] + [0]*(len(w) - 1)
340
+ for d in range(max(1, maxsqrtn)):
341
+ t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
342
+ g = guess_generating_function_rational(t, X=X)
343
+ if g:
344
+ result['hlgf'] = g**Rational(1, d+1)
345
+ break
346
+
347
+ # Logarithmic derivative of ordinary generating Function (lgdogf)
348
+ if v[0] != 0 and ('lgdogf' in types
349
+ or ('ogf' in types and 'ogf' not in result)):
350
+ # Transform sequence by computing f'(x)/f(x)
351
+ # because log(f(x)) = integrate( f'(x)/f(x) )
352
+ a, w = sympify(v[0]), []
353
+ for n in range(len(v)-1):
354
+ w.append(
355
+ (v[n+1]*(n+1) - sum(w[-i-1]*v[i+1] for i in range(n)))/a)
356
+ # Perform some convolutions of the sequence with itself
357
+ t = [1] + [0]*(len(w) - 1)
358
+ for d in range(max(1, maxsqrtn)):
359
+ t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
360
+ g = guess_generating_function_rational(t, X=X)
361
+ if g:
362
+ result['lgdogf'] = g**Rational(1, d+1)
363
+ if 'ogf' not in result:
364
+ result['ogf'] = exp(integrate(result['lgdogf'], X))
365
+ break
366
+
367
+ # Logarithmic derivative of exponential generating Function (lgdegf)
368
+ if v[0] != 0 and ('lgdegf' in types
369
+ or ('egf' in types and 'egf' not in result)):
370
+ # Transform sequence / step 1 (division by factorial)
371
+ z, f = [], S.One
372
+ for i, k in enumerate(v):
373
+ f *= i if i else 1
374
+ z.append(k/f)
375
+ # Transform sequence / step 2 by computing f'(x)/f(x)
376
+ # because log(f(x)) = integrate( f'(x)/f(x) )
377
+ a, w = z[0], []
378
+ for n in range(len(z)-1):
379
+ w.append(
380
+ (z[n+1]*(n+1) - sum(w[-i-1]*z[i+1] for i in range(n)))/a)
381
+ # Perform some convolutions of the sequence with itself
382
+ t = [1] + [0]*(len(w) - 1)
383
+ for d in range(max(1, maxsqrtn)):
384
+ t = [sum(t[n-i]*w[i] for i in range(n+1)) for n in range(len(w))]
385
+ g = guess_generating_function_rational(t, X=X)
386
+ if g:
387
+ result['lgdegf'] = g**Rational(1, d+1)
388
+ if 'egf' not in result:
389
+ result['egf'] = exp(integrate(result['lgdegf'], X))
390
+ break
391
+
392
+ return result
393
+
394
+
395
+ @public
396
+ def guess(l, all=False, evaluate=True, niter=2, variables=None):
397
+ """
398
+ This function is adapted from the Rate.m package for Mathematica
399
+ written by Christian Krattenthaler.
400
+ It tries to guess a formula from a given sequence of rational numbers.
401
+
402
+ Explanation
403
+ ===========
404
+
405
+ In order to speed up the process, the 'all' variable is set to False by
406
+ default, stopping the computation as some results are returned during an
407
+ iteration; the variable can be set to True if more iterations are needed
408
+ (other formulas may be found; however they may be equivalent to the first
409
+ ones).
410
+
411
+ Another option is the 'evaluate' variable (default is True); setting it
412
+ to False will leave the involved products unevaluated.
413
+
414
+ By default, the number of iterations is set to 2 but a greater value (up
415
+ to len(l)-1) can be specified with the optional 'niter' variable.
416
+ More and more convoluted results are found when the order of the
417
+ iteration gets higher:
418
+
419
+ * first iteration returns polynomial or rational functions;
420
+ * second iteration returns products of rising factorials and their
421
+ inverses;
422
+ * third iteration returns products of products of rising factorials
423
+ and their inverses;
424
+ * etc.
425
+
426
+ The returned formulas contain symbols i0, i1, i2, ... where the main
427
+ variables is i0 (and auxiliary variables are i1, i2, ...). A list of
428
+ other symbols can be provided in the 'variables' option; the length of
429
+ the least should be the value of 'niter' (more is acceptable but only
430
+ the first symbols will be used); in this case, the main variable will be
431
+ the first symbol in the list.
432
+
433
+ Examples
434
+ ========
435
+
436
+ >>> from sympy.concrete.guess import guess
437
+ >>> guess([1,2,6,24,120], evaluate=False)
438
+ [Product(i1 + 1, (i1, 1, i0 - 1))]
439
+
440
+ >>> from sympy import symbols
441
+ >>> r = guess([1,2,7,42,429,7436,218348,10850216], niter=4)
442
+ >>> i0 = symbols("i0")
443
+ >>> [r[0].subs(i0,n).doit() for n in range(1,10)]
444
+ [1, 2, 7, 42, 429, 7436, 218348, 10850216, 911835460]
445
+ """
446
+ if any(a==0 for a in l[:-1]):
447
+ return []
448
+ N = len(l)
449
+ niter = min(N-1, niter)
450
+ myprod = product if evaluate else Product
451
+ g = []
452
+ res = []
453
+ if variables is None:
454
+ symb = symbols('i:'+str(niter))
455
+ else:
456
+ symb = variables
457
+ for k, s in enumerate(symb):
458
+ g.append(l)
459
+ n, r = len(l), []
460
+ for i in range(n-2-1, -1, -1):
461
+ ri = rinterp(enumerate(g[k][:-1], start=1), i, X=s)
462
+ if ((denom(ri).subs({s:n}) != 0)
463
+ and (ri.subs({s:n}) - g[k][-1] == 0)
464
+ and ri not in r):
465
+ r.append(ri)
466
+ if r:
467
+ for i in range(k-1, -1, -1):
468
+ r = [g[i][0]
469
+ * myprod(v, (symb[i+1], 1, symb[i]-1)) for v in r]
470
+ if not all: return r
471
+ res += r
472
+ l = [Rational(l[i+1], l[i]) for i in range(N-k-1)]
473
+ return res
lib/python3.12/site-packages/sympy/concrete/products.py ADDED
@@ -0,0 +1,610 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple as tTuple
2
+
3
+ from .expr_with_intlimits import ExprWithIntLimits
4
+ from .summations import Sum, summation, _dummy_with_inherited_properties_concrete
5
+ from sympy.core.expr import Expr
6
+ from sympy.core.exprtools import factor_terms
7
+ from sympy.core.function import Derivative
8
+ from sympy.core.mul import Mul
9
+ from sympy.core.singleton import S
10
+ from sympy.core.symbol import Dummy, Symbol
11
+ from sympy.functions.combinatorial.factorials import RisingFactorial
12
+ from sympy.functions.elementary.exponential import exp, log
13
+ from sympy.functions.special.tensor_functions import KroneckerDelta
14
+ from sympy.polys import quo, roots
15
+
16
+
17
+ class Product(ExprWithIntLimits):
18
+ r"""
19
+ Represents unevaluated products.
20
+
21
+ Explanation
22
+ ===========
23
+
24
+ ``Product`` represents a finite or infinite product, with the first
25
+ argument being the general form of terms in the series, and the second
26
+ argument being ``(dummy_variable, start, end)``, with ``dummy_variable``
27
+ taking all integer values from ``start`` through ``end``. In accordance
28
+ with long-standing mathematical convention, the end term is included in
29
+ the product.
30
+
31
+ Finite products
32
+ ===============
33
+
34
+ For finite products (and products with symbolic limits assumed to be finite)
35
+ we follow the analogue of the summation convention described by Karr [1],
36
+ especially definition 3 of section 1.4. The product:
37
+
38
+ .. math::
39
+
40
+ \prod_{m \leq i < n} f(i)
41
+
42
+ has *the obvious meaning* for `m < n`, namely:
43
+
44
+ .. math::
45
+
46
+ \prod_{m \leq i < n} f(i) = f(m) f(m+1) \cdot \ldots \cdot f(n-2) f(n-1)
47
+
48
+ with the upper limit value `f(n)` excluded. The product over an empty set is
49
+ one if and only if `m = n`:
50
+
51
+ .. math::
52
+
53
+ \prod_{m \leq i < n} f(i) = 1 \quad \mathrm{for} \quad m = n
54
+
55
+ Finally, for all other products over empty sets we assume the following
56
+ definition:
57
+
58
+ .. math::
59
+
60
+ \prod_{m \leq i < n} f(i) = \frac{1}{\prod_{n \leq i < m} f(i)} \quad \mathrm{for} \quad m > n
61
+
62
+ It is important to note that above we define all products with the upper
63
+ limit being exclusive. This is in contrast to the usual mathematical notation,
64
+ but does not affect the product convention. Indeed we have:
65
+
66
+ .. math::
67
+
68
+ \prod_{m \leq i < n} f(i) = \prod_{i = m}^{n - 1} f(i)
69
+
70
+ where the difference in notation is intentional to emphasize the meaning,
71
+ with limits typeset on the top being inclusive.
72
+
73
+ Examples
74
+ ========
75
+
76
+ >>> from sympy.abc import a, b, i, k, m, n, x
77
+ >>> from sympy import Product, oo
78
+ >>> Product(k, (k, 1, m))
79
+ Product(k, (k, 1, m))
80
+ >>> Product(k, (k, 1, m)).doit()
81
+ factorial(m)
82
+ >>> Product(k**2,(k, 1, m))
83
+ Product(k**2, (k, 1, m))
84
+ >>> Product(k**2,(k, 1, m)).doit()
85
+ factorial(m)**2
86
+
87
+ Wallis' product for pi:
88
+
89
+ >>> W = Product(2*i/(2*i-1) * 2*i/(2*i+1), (i, 1, oo))
90
+ >>> W
91
+ Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo))
92
+
93
+ Direct computation currently fails:
94
+
95
+ >>> W.doit()
96
+ Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, oo))
97
+
98
+ But we can approach the infinite product by a limit of finite products:
99
+
100
+ >>> from sympy import limit
101
+ >>> W2 = Product(2*i/(2*i-1)*2*i/(2*i+1), (i, 1, n))
102
+ >>> W2
103
+ Product(4*i**2/((2*i - 1)*(2*i + 1)), (i, 1, n))
104
+ >>> W2e = W2.doit()
105
+ >>> W2e
106
+ 4**n*factorial(n)**2/(2**(2*n)*RisingFactorial(1/2, n)*RisingFactorial(3/2, n))
107
+ >>> limit(W2e, n, oo)
108
+ pi/2
109
+
110
+ By the same formula we can compute sin(pi/2):
111
+
112
+ >>> from sympy import combsimp, pi, gamma, simplify
113
+ >>> P = pi * x * Product(1 - x**2/k**2, (k, 1, n))
114
+ >>> P = P.subs(x, pi/2)
115
+ >>> P
116
+ pi**2*Product(1 - pi**2/(4*k**2), (k, 1, n))/2
117
+ >>> Pe = P.doit()
118
+ >>> Pe
119
+ pi**2*RisingFactorial(1 - pi/2, n)*RisingFactorial(1 + pi/2, n)/(2*factorial(n)**2)
120
+ >>> limit(Pe, n, oo).gammasimp()
121
+ sin(pi**2/2)
122
+ >>> Pe.rewrite(gamma)
123
+ (-1)**n*pi**2*gamma(pi/2)*gamma(n + 1 + pi/2)/(2*gamma(1 + pi/2)*gamma(-n + pi/2)*gamma(n + 1)**2)
124
+
125
+ Products with the lower limit being larger than the upper one:
126
+
127
+ >>> Product(1/i, (i, 6, 1)).doit()
128
+ 120
129
+ >>> Product(i, (i, 2, 5)).doit()
130
+ 120
131
+
132
+ The empty product:
133
+
134
+ >>> Product(i, (i, n, n-1)).doit()
135
+ 1
136
+
137
+ An example showing that the symbolic result of a product is still
138
+ valid for seemingly nonsensical values of the limits. Then the Karr
139
+ convention allows us to give a perfectly valid interpretation to
140
+ those products by interchanging the limits according to the above rules:
141
+
142
+ >>> P = Product(2, (i, 10, n)).doit()
143
+ >>> P
144
+ 2**(n - 9)
145
+ >>> P.subs(n, 5)
146
+ 1/16
147
+ >>> Product(2, (i, 10, 5)).doit()
148
+ 1/16
149
+ >>> 1/Product(2, (i, 6, 9)).doit()
150
+ 1/16
151
+
152
+ An explicit example of the Karr summation convention applied to products:
153
+
154
+ >>> P1 = Product(x, (i, a, b)).doit()
155
+ >>> P1
156
+ x**(-a + b + 1)
157
+ >>> P2 = Product(x, (i, b+1, a-1)).doit()
158
+ >>> P2
159
+ x**(a - b - 1)
160
+ >>> simplify(P1 * P2)
161
+ 1
162
+
163
+ And another one:
164
+
165
+ >>> P1 = Product(i, (i, b, a)).doit()
166
+ >>> P1
167
+ RisingFactorial(b, a - b + 1)
168
+ >>> P2 = Product(i, (i, a+1, b-1)).doit()
169
+ >>> P2
170
+ RisingFactorial(a + 1, -a + b - 1)
171
+ >>> P1 * P2
172
+ RisingFactorial(b, a - b + 1)*RisingFactorial(a + 1, -a + b - 1)
173
+ >>> combsimp(P1 * P2)
174
+ 1
175
+
176
+ See Also
177
+ ========
178
+
179
+ Sum, summation
180
+ product
181
+
182
+ References
183
+ ==========
184
+
185
+ .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
186
+ Volume 28 Issue 2, April 1981, Pages 305-350
187
+ https://dl.acm.org/doi/10.1145/322248.322255
188
+ .. [2] https://en.wikipedia.org/wiki/Multiplication#Capital_Pi_notation
189
+ .. [3] https://en.wikipedia.org/wiki/Empty_product
190
+ """
191
+
192
+ __slots__ = ()
193
+
194
+ limits: tTuple[tTuple[Symbol, Expr, Expr]]
195
+
196
+ def __new__(cls, function, *symbols, **assumptions):
197
+ obj = ExprWithIntLimits.__new__(cls, function, *symbols, **assumptions)
198
+ return obj
199
+
200
+ def _eval_rewrite_as_Sum(self, *args, **kwargs):
201
+ return exp(Sum(log(self.function), *self.limits))
202
+
203
+ @property
204
+ def term(self):
205
+ return self._args[0]
206
+ function = term
207
+
208
+ def _eval_is_zero(self):
209
+ if self.has_empty_sequence:
210
+ return False
211
+
212
+ z = self.term.is_zero
213
+ if z is True:
214
+ return True
215
+ if self.has_finite_limits:
216
+ # A Product is zero only if its term is zero assuming finite limits.
217
+ return z
218
+
219
+ def _eval_is_extended_real(self):
220
+ if self.has_empty_sequence:
221
+ return True
222
+
223
+ return self.function.is_extended_real
224
+
225
+ def _eval_is_positive(self):
226
+ if self.has_empty_sequence:
227
+ return True
228
+ if self.function.is_positive and self.has_finite_limits:
229
+ return True
230
+
231
+ def _eval_is_nonnegative(self):
232
+ if self.has_empty_sequence:
233
+ return True
234
+ if self.function.is_nonnegative and self.has_finite_limits:
235
+ return True
236
+
237
+ def _eval_is_extended_nonnegative(self):
238
+ if self.has_empty_sequence:
239
+ return True
240
+ if self.function.is_extended_nonnegative:
241
+ return True
242
+
243
+ def _eval_is_extended_nonpositive(self):
244
+ if self.has_empty_sequence:
245
+ return True
246
+
247
+ def _eval_is_finite(self):
248
+ if self.has_finite_limits and self.function.is_finite:
249
+ return True
250
+
251
+ def doit(self, **hints):
252
+ # first make sure any definite limits have product
253
+ # variables with matching assumptions
254
+ reps = {}
255
+ for xab in self.limits:
256
+ d = _dummy_with_inherited_properties_concrete(xab)
257
+ if d:
258
+ reps[xab[0]] = d
259
+ if reps:
260
+ undo = {v: k for k, v in reps.items()}
261
+ did = self.xreplace(reps).doit(**hints)
262
+ if isinstance(did, tuple): # when separate=True
263
+ did = tuple([i.xreplace(undo) for i in did])
264
+ else:
265
+ did = did.xreplace(undo)
266
+ return did
267
+
268
+ from sympy.simplify.powsimp import powsimp
269
+ f = self.function
270
+ for index, limit in enumerate(self.limits):
271
+ i, a, b = limit
272
+ dif = b - a
273
+ if dif.is_integer and dif.is_negative:
274
+ a, b = b + 1, a - 1
275
+ f = 1 / f
276
+
277
+ g = self._eval_product(f, (i, a, b))
278
+ if g in (None, S.NaN):
279
+ return self.func(powsimp(f), *self.limits[index:])
280
+ else:
281
+ f = g
282
+
283
+ if hints.get('deep', True):
284
+ return f.doit(**hints)
285
+ else:
286
+ return powsimp(f)
287
+
288
+ def _eval_adjoint(self):
289
+ if self.is_commutative:
290
+ return self.func(self.function.adjoint(), *self.limits)
291
+ return None
292
+
293
+ def _eval_conjugate(self):
294
+ return self.func(self.function.conjugate(), *self.limits)
295
+
296
+ def _eval_product(self, term, limits):
297
+
298
+ (k, a, n) = limits
299
+
300
+ if k not in term.free_symbols:
301
+ if (term - 1).is_zero:
302
+ return S.One
303
+ return term**(n - a + 1)
304
+
305
+ if a == n:
306
+ return term.subs(k, a)
307
+
308
+ from .delta import deltaproduct, _has_simple_delta
309
+ if term.has(KroneckerDelta) and _has_simple_delta(term, limits[0]):
310
+ return deltaproduct(term, limits)
311
+
312
+ dif = n - a
313
+ definite = dif.is_Integer
314
+ if definite and (dif < 100):
315
+ return self._eval_product_direct(term, limits)
316
+
317
+ elif term.is_polynomial(k):
318
+ poly = term.as_poly(k)
319
+
320
+ A = B = Q = S.One
321
+
322
+ all_roots = roots(poly)
323
+
324
+ M = 0
325
+ for r, m in all_roots.items():
326
+ M += m
327
+ A *= RisingFactorial(a - r, n - a + 1)**m
328
+ Q *= (n - r)**m
329
+
330
+ if M < poly.degree():
331
+ arg = quo(poly, Q.as_poly(k))
332
+ B = self.func(arg, (k, a, n)).doit()
333
+
334
+ return poly.LC()**(n - a + 1) * A * B
335
+
336
+ elif term.is_Add:
337
+ factored = factor_terms(term, fraction=True)
338
+ if factored.is_Mul:
339
+ return self._eval_product(factored, (k, a, n))
340
+
341
+ elif term.is_Mul:
342
+ # Factor in part without the summation variable and part with
343
+ without_k, with_k = term.as_coeff_mul(k)
344
+
345
+ if len(with_k) >= 2:
346
+ # More than one term including k, so still a multiplication
347
+ exclude, include = [], []
348
+ for t in with_k:
349
+ p = self._eval_product(t, (k, a, n))
350
+
351
+ if p is not None:
352
+ exclude.append(p)
353
+ else:
354
+ include.append(t)
355
+
356
+ if not exclude:
357
+ return None
358
+ else:
359
+ arg = term._new_rawargs(*include)
360
+ A = Mul(*exclude)
361
+ B = self.func(arg, (k, a, n)).doit()
362
+ return without_k**(n - a + 1)*A * B
363
+ else:
364
+ # Just a single term
365
+ p = self._eval_product(with_k[0], (k, a, n))
366
+ if p is None:
367
+ p = self.func(with_k[0], (k, a, n)).doit()
368
+ return without_k**(n - a + 1)*p
369
+
370
+
371
+ elif term.is_Pow:
372
+ if not term.base.has(k):
373
+ s = summation(term.exp, (k, a, n))
374
+
375
+ return term.base**s
376
+ elif not term.exp.has(k):
377
+ p = self._eval_product(term.base, (k, a, n))
378
+
379
+ if p is not None:
380
+ return p**term.exp
381
+
382
+ elif isinstance(term, Product):
383
+ evaluated = term.doit()
384
+ f = self._eval_product(evaluated, limits)
385
+ if f is None:
386
+ return self.func(evaluated, limits)
387
+ else:
388
+ return f
389
+
390
+ if definite:
391
+ return self._eval_product_direct(term, limits)
392
+
393
+ def _eval_simplify(self, **kwargs):
394
+ from sympy.simplify.simplify import product_simplify
395
+ rv = product_simplify(self, **kwargs)
396
+ return rv.doit() if kwargs['doit'] else rv
397
+
398
+ def _eval_transpose(self):
399
+ if self.is_commutative:
400
+ return self.func(self.function.transpose(), *self.limits)
401
+ return None
402
+
403
+ def _eval_product_direct(self, term, limits):
404
+ (k, a, n) = limits
405
+ return Mul(*[term.subs(k, a + i) for i in range(n - a + 1)])
406
+
407
+ def _eval_derivative(self, x):
408
+ if isinstance(x, Symbol) and x not in self.free_symbols:
409
+ return S.Zero
410
+ f, limits = self.function, list(self.limits)
411
+ limit = limits.pop(-1)
412
+ if limits:
413
+ f = self.func(f, *limits)
414
+ i, a, b = limit
415
+ if x in a.free_symbols or x in b.free_symbols:
416
+ return None
417
+ h = Dummy()
418
+ rv = Sum( Product(f, (i, a, h - 1)) * Product(f, (i, h + 1, b)) * Derivative(f, x, evaluate=True).subs(i, h), (h, a, b))
419
+ return rv
420
+
421
+ def is_convergent(self):
422
+ r"""
423
+ See docs of :obj:`.Sum.is_convergent()` for explanation of convergence
424
+ in SymPy.
425
+
426
+ Explanation
427
+ ===========
428
+
429
+ The infinite product:
430
+
431
+ .. math::
432
+
433
+ \prod_{1 \leq i < \infty} f(i)
434
+
435
+ is defined by the sequence of partial products:
436
+
437
+ .. math::
438
+
439
+ \prod_{i=1}^{n} f(i) = f(1) f(2) \cdots f(n)
440
+
441
+ as n increases without bound. The product converges to a non-zero
442
+ value if and only if the sum:
443
+
444
+ .. math::
445
+
446
+ \sum_{1 \leq i < \infty} \log{f(n)}
447
+
448
+ converges.
449
+
450
+ Examples
451
+ ========
452
+
453
+ >>> from sympy import Product, Symbol, cos, pi, exp, oo
454
+ >>> n = Symbol('n', integer=True)
455
+ >>> Product(n/(n + 1), (n, 1, oo)).is_convergent()
456
+ False
457
+ >>> Product(1/n**2, (n, 1, oo)).is_convergent()
458
+ False
459
+ >>> Product(cos(pi/n), (n, 1, oo)).is_convergent()
460
+ True
461
+ >>> Product(exp(-n**2), (n, 1, oo)).is_convergent()
462
+ False
463
+
464
+ References
465
+ ==========
466
+
467
+ .. [1] https://en.wikipedia.org/wiki/Infinite_product
468
+ """
469
+ sequence_term = self.function
470
+ log_sum = log(sequence_term)
471
+ lim = self.limits
472
+ try:
473
+ is_conv = Sum(log_sum, *lim).is_convergent()
474
+ except NotImplementedError:
475
+ if Sum(sequence_term - 1, *lim).is_absolutely_convergent() is S.true:
476
+ return S.true
477
+ raise NotImplementedError("The algorithm to find the product convergence of %s "
478
+ "is not yet implemented" % (sequence_term))
479
+ return is_conv
480
+
481
+ def reverse_order(expr, *indices):
482
+ """
483
+ Reverse the order of a limit in a Product.
484
+
485
+ Explanation
486
+ ===========
487
+
488
+ ``reverse_order(expr, *indices)`` reverses some limits in the expression
489
+ ``expr`` which can be either a ``Sum`` or a ``Product``. The selectors in
490
+ the argument ``indices`` specify some indices whose limits get reversed.
491
+ These selectors are either variable names or numerical indices counted
492
+ starting from the inner-most limit tuple.
493
+
494
+ Examples
495
+ ========
496
+
497
+ >>> from sympy import gamma, Product, simplify, Sum
498
+ >>> from sympy.abc import x, y, a, b, c, d
499
+ >>> P = Product(x, (x, a, b))
500
+ >>> Pr = P.reverse_order(x)
501
+ >>> Pr
502
+ Product(1/x, (x, b + 1, a - 1))
503
+ >>> Pr = Pr.doit()
504
+ >>> Pr
505
+ 1/RisingFactorial(b + 1, a - b - 1)
506
+ >>> simplify(Pr.rewrite(gamma))
507
+ Piecewise((gamma(b + 1)/gamma(a), b > -1), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True))
508
+ >>> P = P.doit()
509
+ >>> P
510
+ RisingFactorial(a, -a + b + 1)
511
+ >>> simplify(P.rewrite(gamma))
512
+ Piecewise((gamma(b + 1)/gamma(a), a > 0), ((-1)**(-a + b + 1)*gamma(1 - a)/gamma(-b), True))
513
+
514
+ While one should prefer variable names when specifying which limits
515
+ to reverse, the index counting notation comes in handy in case there
516
+ are several symbols with the same name.
517
+
518
+ >>> S = Sum(x*y, (x, a, b), (y, c, d))
519
+ >>> S
520
+ Sum(x*y, (x, a, b), (y, c, d))
521
+ >>> S0 = S.reverse_order(0)
522
+ >>> S0
523
+ Sum(-x*y, (x, b + 1, a - 1), (y, c, d))
524
+ >>> S1 = S0.reverse_order(1)
525
+ >>> S1
526
+ Sum(x*y, (x, b + 1, a - 1), (y, d + 1, c - 1))
527
+
528
+ Of course we can mix both notations:
529
+
530
+ >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1)
531
+ Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
532
+ >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x)
533
+ Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
534
+
535
+ See Also
536
+ ========
537
+
538
+ sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index,
539
+ reorder_limit,
540
+ sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder
541
+
542
+ References
543
+ ==========
544
+
545
+ .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
546
+ Volume 28 Issue 2, April 1981, Pages 305-350
547
+ https://dl.acm.org/doi/10.1145/322248.322255
548
+
549
+ """
550
+ l_indices = list(indices)
551
+
552
+ for i, indx in enumerate(l_indices):
553
+ if not isinstance(indx, int):
554
+ l_indices[i] = expr.index(indx)
555
+
556
+ e = 1
557
+ limits = []
558
+ for i, limit in enumerate(expr.limits):
559
+ l = limit
560
+ if i in l_indices:
561
+ e = -e
562
+ l = (limit[0], limit[2] + 1, limit[1] - 1)
563
+ limits.append(l)
564
+
565
+ return Product(expr.function ** e, *limits)
566
+
567
+
568
+ def product(*args, **kwargs):
569
+ r"""
570
+ Compute the product.
571
+
572
+ Explanation
573
+ ===========
574
+
575
+ The notation for symbols is similar to the notation used in Sum or
576
+ Integral. product(f, (i, a, b)) computes the product of f with
577
+ respect to i from a to b, i.e.,
578
+
579
+ ::
580
+
581
+ b
582
+ _____
583
+ product(f(n), (i, a, b)) = | | f(n)
584
+ | |
585
+ i = a
586
+
587
+ If it cannot compute the product, it returns an unevaluated Product object.
588
+ Repeated products can be computed by introducing additional symbols tuples::
589
+
590
+ Examples
591
+ ========
592
+
593
+ >>> from sympy import product, symbols
594
+ >>> i, n, m, k = symbols('i n m k', integer=True)
595
+
596
+ >>> product(i, (i, 1, k))
597
+ factorial(k)
598
+ >>> product(m, (i, 1, k))
599
+ m**k
600
+ >>> product(i, (i, 1, k), (k, 1, n))
601
+ Product(factorial(k), (k, 1, n))
602
+
603
+ """
604
+
605
+ prod = Product(*args, **kwargs)
606
+
607
+ if isinstance(prod, Product):
608
+ return prod.doit(deep=False)
609
+ else:
610
+ return prod
lib/python3.12/site-packages/sympy/concrete/summations.py ADDED
@@ -0,0 +1,1646 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Tuple as tTuple
2
+
3
+ from sympy.calculus.singularities import is_decreasing
4
+ from sympy.calculus.accumulationbounds import AccumulationBounds
5
+ from .expr_with_intlimits import ExprWithIntLimits
6
+ from .expr_with_limits import AddWithLimits
7
+ from .gosper import gosper_sum
8
+ from sympy.core.expr import Expr
9
+ from sympy.core.add import Add
10
+ from sympy.core.containers import Tuple
11
+ from sympy.core.function import Derivative, expand
12
+ from sympy.core.mul import Mul
13
+ from sympy.core.numbers import Float, _illegal
14
+ from sympy.core.relational import Eq
15
+ from sympy.core.singleton import S
16
+ from sympy.core.sorting import ordered
17
+ from sympy.core.symbol import Dummy, Wild, Symbol, symbols
18
+ from sympy.functions.combinatorial.factorials import factorial
19
+ from sympy.functions.combinatorial.numbers import bernoulli, harmonic
20
+ from sympy.functions.elementary.exponential import exp, log
21
+ from sympy.functions.elementary.piecewise import Piecewise
22
+ from sympy.functions.elementary.trigonometric import cot, csc
23
+ from sympy.functions.special.hyper import hyper
24
+ from sympy.functions.special.tensor_functions import KroneckerDelta
25
+ from sympy.functions.special.zeta_functions import zeta
26
+ from sympy.integrals.integrals import Integral
27
+ from sympy.logic.boolalg import And
28
+ from sympy.polys.partfrac import apart
29
+ from sympy.polys.polyerrors import PolynomialError, PolificationFailed
30
+ from sympy.polys.polytools import parallel_poly_from_expr, Poly, factor
31
+ from sympy.polys.rationaltools import together
32
+ from sympy.series.limitseq import limit_seq
33
+ from sympy.series.order import O
34
+ from sympy.series.residues import residue
35
+ from sympy.sets.sets import FiniteSet, Interval
36
+ from sympy.utilities.iterables import sift
37
+ import itertools
38
+
39
+
40
+ class Sum(AddWithLimits, ExprWithIntLimits):
41
+ r"""
42
+ Represents unevaluated summation.
43
+
44
+ Explanation
45
+ ===========
46
+
47
+ ``Sum`` represents a finite or infinite series, with the first argument
48
+ being the general form of terms in the series, and the second argument
49
+ being ``(dummy_variable, start, end)``, with ``dummy_variable`` taking
50
+ all integer values from ``start`` through ``end``. In accordance with
51
+ long-standing mathematical convention, the end term is included in the
52
+ summation.
53
+
54
+ Finite sums
55
+ ===========
56
+
57
+ For finite sums (and sums with symbolic limits assumed to be finite) we
58
+ follow the summation convention described by Karr [1], especially
59
+ definition 3 of section 1.4. The sum:
60
+
61
+ .. math::
62
+
63
+ \sum_{m \leq i < n} f(i)
64
+
65
+ has *the obvious meaning* for `m < n`, namely:
66
+
67
+ .. math::
68
+
69
+ \sum_{m \leq i < n} f(i) = f(m) + f(m+1) + \ldots + f(n-2) + f(n-1)
70
+
71
+ with the upper limit value `f(n)` excluded. The sum over an empty set is
72
+ zero if and only if `m = n`:
73
+
74
+ .. math::
75
+
76
+ \sum_{m \leq i < n} f(i) = 0 \quad \mathrm{for} \quad m = n
77
+
78
+ Finally, for all other sums over empty sets we assume the following
79
+ definition:
80
+
81
+ .. math::
82
+
83
+ \sum_{m \leq i < n} f(i) = - \sum_{n \leq i < m} f(i) \quad \mathrm{for} \quad m > n
84
+
85
+ It is important to note that Karr defines all sums with the upper
86
+ limit being exclusive. This is in contrast to the usual mathematical notation,
87
+ but does not affect the summation convention. Indeed we have:
88
+
89
+ .. math::
90
+
91
+ \sum_{m \leq i < n} f(i) = \sum_{i = m}^{n - 1} f(i)
92
+
93
+ where the difference in notation is intentional to emphasize the meaning,
94
+ with limits typeset on the top being inclusive.
95
+
96
+ Examples
97
+ ========
98
+
99
+ >>> from sympy.abc import i, k, m, n, x
100
+ >>> from sympy import Sum, factorial, oo, IndexedBase, Function
101
+ >>> Sum(k, (k, 1, m))
102
+ Sum(k, (k, 1, m))
103
+ >>> Sum(k, (k, 1, m)).doit()
104
+ m**2/2 + m/2
105
+ >>> Sum(k**2, (k, 1, m))
106
+ Sum(k**2, (k, 1, m))
107
+ >>> Sum(k**2, (k, 1, m)).doit()
108
+ m**3/3 + m**2/2 + m/6
109
+ >>> Sum(x**k, (k, 0, oo))
110
+ Sum(x**k, (k, 0, oo))
111
+ >>> Sum(x**k, (k, 0, oo)).doit()
112
+ Piecewise((1/(1 - x), Abs(x) < 1), (Sum(x**k, (k, 0, oo)), True))
113
+ >>> Sum(x**k/factorial(k), (k, 0, oo)).doit()
114
+ exp(x)
115
+
116
+ Here are examples to do summation with symbolic indices. You
117
+ can use either Function of IndexedBase classes:
118
+
119
+ >>> f = Function('f')
120
+ >>> Sum(f(n), (n, 0, 3)).doit()
121
+ f(0) + f(1) + f(2) + f(3)
122
+ >>> Sum(f(n), (n, 0, oo)).doit()
123
+ Sum(f(n), (n, 0, oo))
124
+ >>> f = IndexedBase('f')
125
+ >>> Sum(f[n]**2, (n, 0, 3)).doit()
126
+ f[0]**2 + f[1]**2 + f[2]**2 + f[3]**2
127
+
128
+ An example showing that the symbolic result of a summation is still
129
+ valid for seemingly nonsensical values of the limits. Then the Karr
130
+ convention allows us to give a perfectly valid interpretation to
131
+ those sums by interchanging the limits according to the above rules:
132
+
133
+ >>> S = Sum(i, (i, 1, n)).doit()
134
+ >>> S
135
+ n**2/2 + n/2
136
+ >>> S.subs(n, -4)
137
+ 6
138
+ >>> Sum(i, (i, 1, -4)).doit()
139
+ 6
140
+ >>> Sum(-i, (i, -3, 0)).doit()
141
+ 6
142
+
143
+ An explicit example of the Karr summation convention:
144
+
145
+ >>> S1 = Sum(i**2, (i, m, m+n-1)).doit()
146
+ >>> S1
147
+ m**2*n + m*n**2 - m*n + n**3/3 - n**2/2 + n/6
148
+ >>> S2 = Sum(i**2, (i, m+n, m-1)).doit()
149
+ >>> S2
150
+ -m**2*n - m*n**2 + m*n - n**3/3 + n**2/2 - n/6
151
+ >>> S1 + S2
152
+ 0
153
+ >>> S3 = Sum(i, (i, m, m-1)).doit()
154
+ >>> S3
155
+ 0
156
+
157
+ See Also
158
+ ========
159
+
160
+ summation
161
+ Product, sympy.concrete.products.product
162
+
163
+ References
164
+ ==========
165
+
166
+ .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
167
+ Volume 28 Issue 2, April 1981, Pages 305-350
168
+ https://dl.acm.org/doi/10.1145/322248.322255
169
+ .. [2] https://en.wikipedia.org/wiki/Summation#Capital-sigma_notation
170
+ .. [3] https://en.wikipedia.org/wiki/Empty_sum
171
+ """
172
+
173
+ __slots__ = ()
174
+
175
+ limits: tTuple[tTuple[Symbol, Expr, Expr]]
176
+
177
+ def __new__(cls, function, *symbols, **assumptions):
178
+ obj = AddWithLimits.__new__(cls, function, *symbols, **assumptions)
179
+ if not hasattr(obj, 'limits'):
180
+ return obj
181
+ if any(len(l) != 3 or None in l for l in obj.limits):
182
+ raise ValueError('Sum requires values for lower and upper bounds.')
183
+
184
+ return obj
185
+
186
+ def _eval_is_zero(self):
187
+ # a Sum is only zero if its function is zero or if all terms
188
+ # cancel out. This only answers whether the summand is zero; if
189
+ # not then None is returned since we don't analyze whether all
190
+ # terms cancel out.
191
+ if self.function.is_zero or self.has_empty_sequence:
192
+ return True
193
+
194
+ def _eval_is_extended_real(self):
195
+ if self.has_empty_sequence:
196
+ return True
197
+ return self.function.is_extended_real
198
+
199
+ def _eval_is_positive(self):
200
+ if self.has_finite_limits and self.has_reversed_limits is False:
201
+ return self.function.is_positive
202
+
203
+ def _eval_is_negative(self):
204
+ if self.has_finite_limits and self.has_reversed_limits is False:
205
+ return self.function.is_negative
206
+
207
+ def _eval_is_finite(self):
208
+ if self.has_finite_limits and self.function.is_finite:
209
+ return True
210
+
211
+ def doit(self, **hints):
212
+ if hints.get('deep', True):
213
+ f = self.function.doit(**hints)
214
+ else:
215
+ f = self.function
216
+
217
+ # first make sure any definite limits have summation
218
+ # variables with matching assumptions
219
+ reps = {}
220
+ for xab in self.limits:
221
+ d = _dummy_with_inherited_properties_concrete(xab)
222
+ if d:
223
+ reps[xab[0]] = d
224
+ if reps:
225
+ undo = {v: k for k, v in reps.items()}
226
+ did = self.xreplace(reps).doit(**hints)
227
+ if isinstance(did, tuple): # when separate=True
228
+ did = tuple([i.xreplace(undo) for i in did])
229
+ elif did is not None:
230
+ did = did.xreplace(undo)
231
+ else:
232
+ did = self
233
+ return did
234
+
235
+
236
+ if self.function.is_Matrix:
237
+ expanded = self.expand()
238
+ if self != expanded:
239
+ return expanded.doit()
240
+ return _eval_matrix_sum(self)
241
+
242
+ for n, limit in enumerate(self.limits):
243
+ i, a, b = limit
244
+ dif = b - a
245
+ if dif == -1:
246
+ # Any summation over an empty set is zero
247
+ return S.Zero
248
+ if dif.is_integer and dif.is_negative:
249
+ a, b = b + 1, a - 1
250
+ f = -f
251
+
252
+ newf = eval_sum(f, (i, a, b))
253
+ if newf is None:
254
+ if f == self.function:
255
+ zeta_function = self.eval_zeta_function(f, (i, a, b))
256
+ if zeta_function is not None:
257
+ return zeta_function
258
+ return self
259
+ else:
260
+ return self.func(f, *self.limits[n:])
261
+ f = newf
262
+
263
+ if hints.get('deep', True):
264
+ # eval_sum could return partially unevaluated
265
+ # result with Piecewise. In this case we won't
266
+ # doit() recursively.
267
+ if not isinstance(f, Piecewise):
268
+ return f.doit(**hints)
269
+
270
+ return f
271
+
272
+ def eval_zeta_function(self, f, limits):
273
+ """
274
+ Check whether the function matches with the zeta function.
275
+
276
+ If it matches, then return a `Piecewise` expression because
277
+ zeta function does not converge unless `s > 1` and `q > 0`
278
+ """
279
+ i, a, b = limits
280
+ w, y, z = Wild('w', exclude=[i]), Wild('y', exclude=[i]), Wild('z', exclude=[i])
281
+ result = f.match((w * i + y) ** (-z))
282
+ if result is not None and b is S.Infinity:
283
+ coeff = 1 / result[w] ** result[z]
284
+ s = result[z]
285
+ q = result[y] / result[w] + a
286
+ return Piecewise((coeff * zeta(s, q), And(q > 0, s > 1)), (self, True))
287
+
288
+ def _eval_derivative(self, x):
289
+ """
290
+ Differentiate wrt x as long as x is not in the free symbols of any of
291
+ the upper or lower limits.
292
+
293
+ Explanation
294
+ ===========
295
+
296
+ Sum(a*b*x, (x, 1, a)) can be differentiated wrt x or b but not `a`
297
+ since the value of the sum is discontinuous in `a`. In a case
298
+ involving a limit variable, the unevaluated derivative is returned.
299
+ """
300
+
301
+ # diff already confirmed that x is in the free symbols of self, but we
302
+ # don't want to differentiate wrt any free symbol in the upper or lower
303
+ # limits
304
+ # XXX remove this test for free_symbols when the default _eval_derivative is in
305
+ if isinstance(x, Symbol) and x not in self.free_symbols:
306
+ return S.Zero
307
+
308
+ # get limits and the function
309
+ f, limits = self.function, list(self.limits)
310
+
311
+ limit = limits.pop(-1)
312
+
313
+ if limits: # f is the argument to a Sum
314
+ f = self.func(f, *limits)
315
+
316
+ _, a, b = limit
317
+ if x in a.free_symbols or x in b.free_symbols:
318
+ return None
319
+ df = Derivative(f, x, evaluate=True)
320
+ rv = self.func(df, limit)
321
+ return rv
322
+
323
+ def _eval_difference_delta(self, n, step):
324
+ k, _, upper = self.args[-1]
325
+ new_upper = upper.subs(n, n + step)
326
+
327
+ if len(self.args) == 2:
328
+ f = self.args[0]
329
+ else:
330
+ f = self.func(*self.args[:-1])
331
+
332
+ return Sum(f, (k, upper + 1, new_upper)).doit()
333
+
334
+ def _eval_simplify(self, **kwargs):
335
+
336
+ function = self.function
337
+
338
+ if kwargs.get('deep', True):
339
+ function = function.simplify(**kwargs)
340
+
341
+ # split the function into adds
342
+ terms = Add.make_args(expand(function))
343
+ s_t = [] # Sum Terms
344
+ o_t = [] # Other Terms
345
+
346
+ for term in terms:
347
+ if term.has(Sum):
348
+ # if there is an embedded sum here
349
+ # it is of the form x * (Sum(whatever))
350
+ # hence we make a Mul out of it, and simplify all interior sum terms
351
+ subterms = Mul.make_args(expand(term))
352
+ out_terms = []
353
+ for subterm in subterms:
354
+ # go through each term
355
+ if isinstance(subterm, Sum):
356
+ # if it's a sum, simplify it
357
+ out_terms.append(subterm._eval_simplify(**kwargs))
358
+ else:
359
+ # otherwise, add it as is
360
+ out_terms.append(subterm)
361
+
362
+ # turn it back into a Mul
363
+ s_t.append(Mul(*out_terms))
364
+ else:
365
+ o_t.append(term)
366
+
367
+ # next try to combine any interior sums for further simplification
368
+ from sympy.simplify.simplify import factor_sum, sum_combine
369
+ result = Add(sum_combine(s_t), *o_t)
370
+
371
+ return factor_sum(result, limits=self.limits)
372
+
373
+ def is_convergent(self):
374
+ r"""
375
+ Checks for the convergence of a Sum.
376
+
377
+ Explanation
378
+ ===========
379
+
380
+ We divide the study of convergence of infinite sums and products in
381
+ two parts.
382
+
383
+ First Part:
384
+ One part is the question whether all the terms are well defined, i.e.,
385
+ they are finite in a sum and also non-zero in a product. Zero
386
+ is the analogy of (minus) infinity in products as
387
+ :math:`e^{-\infty} = 0`.
388
+
389
+ Second Part:
390
+ The second part is the question of convergence after infinities,
391
+ and zeros in products, have been omitted assuming that their number
392
+ is finite. This means that we only consider the tail of the sum or
393
+ product, starting from some point after which all terms are well
394
+ defined.
395
+
396
+ For example, in a sum of the form:
397
+
398
+ .. math::
399
+
400
+ \sum_{1 \leq i < \infty} \frac{1}{n^2 + an + b}
401
+
402
+ where a and b are numbers. The routine will return true, even if there
403
+ are infinities in the term sequence (at most two). An analogous
404
+ product would be:
405
+
406
+ .. math::
407
+
408
+ \prod_{1 \leq i < \infty} e^{\frac{1}{n^2 + an + b}}
409
+
410
+ This is how convergence is interpreted. It is concerned with what
411
+ happens at the limit. Finding the bad terms is another independent
412
+ matter.
413
+
414
+ Note: It is responsibility of user to see that the sum or product
415
+ is well defined.
416
+
417
+ There are various tests employed to check the convergence like
418
+ divergence test, root test, integral test, alternating series test,
419
+ comparison tests, Dirichlet tests. It returns true if Sum is convergent
420
+ and false if divergent and NotImplementedError if it cannot be checked.
421
+
422
+ References
423
+ ==========
424
+
425
+ .. [1] https://en.wikipedia.org/wiki/Convergence_tests
426
+
427
+ Examples
428
+ ========
429
+
430
+ >>> from sympy import factorial, S, Sum, Symbol, oo
431
+ >>> n = Symbol('n', integer=True)
432
+ >>> Sum(n/(n - 1), (n, 4, 7)).is_convergent()
433
+ True
434
+ >>> Sum(n/(2*n + 1), (n, 1, oo)).is_convergent()
435
+ False
436
+ >>> Sum(factorial(n)/5**n, (n, 1, oo)).is_convergent()
437
+ False
438
+ >>> Sum(1/n**(S(6)/5), (n, 1, oo)).is_convergent()
439
+ True
440
+
441
+ See Also
442
+ ========
443
+
444
+ Sum.is_absolutely_convergent
445
+ sympy.concrete.products.Product.is_convergent
446
+ """
447
+ p, q, r = symbols('p q r', cls=Wild)
448
+
449
+ sym = self.limits[0][0]
450
+ lower_limit = self.limits[0][1]
451
+ upper_limit = self.limits[0][2]
452
+ sequence_term = self.function.simplify()
453
+
454
+ if len(sequence_term.free_symbols) > 1:
455
+ raise NotImplementedError("convergence checking for more than one symbol "
456
+ "containing series is not handled")
457
+
458
+ if lower_limit.is_finite and upper_limit.is_finite:
459
+ return S.true
460
+
461
+ # transform sym -> -sym and swap the upper_limit = S.Infinity
462
+ # and lower_limit = - upper_limit
463
+ if lower_limit is S.NegativeInfinity:
464
+ if upper_limit is S.Infinity:
465
+ return Sum(sequence_term, (sym, 0, S.Infinity)).is_convergent() and \
466
+ Sum(sequence_term, (sym, S.NegativeInfinity, 0)).is_convergent()
467
+ from sympy.simplify.simplify import simplify
468
+ sequence_term = simplify(sequence_term.xreplace({sym: -sym}))
469
+ lower_limit = -upper_limit
470
+ upper_limit = S.Infinity
471
+
472
+ sym_ = Dummy(sym.name, integer=True, positive=True)
473
+ sequence_term = sequence_term.xreplace({sym: sym_})
474
+ sym = sym_
475
+
476
+ interval = Interval(lower_limit, upper_limit)
477
+
478
+ # Piecewise function handle
479
+ if sequence_term.is_Piecewise:
480
+ for func, cond in sequence_term.args:
481
+ # see if it represents something going to oo
482
+ if cond == True or cond.as_set().sup is S.Infinity:
483
+ s = Sum(func, (sym, lower_limit, upper_limit))
484
+ return s.is_convergent()
485
+ return S.true
486
+
487
+ ### -------- Divergence test ----------- ###
488
+ try:
489
+ lim_val = limit_seq(sequence_term, sym)
490
+ if lim_val is not None and lim_val.is_zero is False:
491
+ return S.false
492
+ except NotImplementedError:
493
+ pass
494
+
495
+ try:
496
+ lim_val_abs = limit_seq(abs(sequence_term), sym)
497
+ if lim_val_abs is not None and lim_val_abs.is_zero is False:
498
+ return S.false
499
+ except NotImplementedError:
500
+ pass
501
+
502
+ order = O(sequence_term, (sym, S.Infinity))
503
+
504
+ ### --------- p-series test (1/n**p) ---------- ###
505
+ p_series_test = order.expr.match(sym**p)
506
+ if p_series_test is not None:
507
+ if p_series_test[p] < -1:
508
+ return S.true
509
+ if p_series_test[p] >= -1:
510
+ return S.false
511
+
512
+ ### ------------- comparison test ------------- ###
513
+ # 1/(n**p*log(n)**q*log(log(n))**r) comparison
514
+ n_log_test = (order.expr.match(1/(sym**p*log(1/sym)**q*log(-log(1/sym))**r)) or
515
+ order.expr.match(1/(sym**p*(-log(1/sym))**q*log(-log(1/sym))**r)))
516
+ if n_log_test is not None:
517
+ if (n_log_test[p] > 1 or
518
+ (n_log_test[p] == 1 and n_log_test[q] > 1) or
519
+ (n_log_test[p] == n_log_test[q] == 1 and n_log_test[r] > 1)):
520
+ return S.true
521
+ return S.false
522
+
523
+ ### ------------- Limit comparison test -----------###
524
+ # (1/n) comparison
525
+ try:
526
+ lim_comp = limit_seq(sym*sequence_term, sym)
527
+ if lim_comp is not None and lim_comp.is_number and lim_comp > 0:
528
+ return S.false
529
+ except NotImplementedError:
530
+ pass
531
+
532
+ ### ----------- ratio test ---------------- ###
533
+ next_sequence_term = sequence_term.xreplace({sym: sym + 1})
534
+ from sympy.simplify.combsimp import combsimp
535
+ from sympy.simplify.powsimp import powsimp
536
+ ratio = combsimp(powsimp(next_sequence_term/sequence_term))
537
+ try:
538
+ lim_ratio = limit_seq(ratio, sym)
539
+ if lim_ratio is not None and lim_ratio.is_number:
540
+ if abs(lim_ratio) > 1:
541
+ return S.false
542
+ if abs(lim_ratio) < 1:
543
+ return S.true
544
+ except NotImplementedError:
545
+ lim_ratio = None
546
+
547
+ ### ---------- Raabe's test -------------- ###
548
+ if lim_ratio == 1: # ratio test inconclusive
549
+ test_val = sym*(sequence_term/
550
+ sequence_term.subs(sym, sym + 1) - 1)
551
+ test_val = test_val.gammasimp()
552
+ try:
553
+ lim_val = limit_seq(test_val, sym)
554
+ if lim_val is not None and lim_val.is_number:
555
+ if lim_val > 1:
556
+ return S.true
557
+ if lim_val < 1:
558
+ return S.false
559
+ except NotImplementedError:
560
+ pass
561
+
562
+ ### ----------- root test ---------------- ###
563
+ # lim = Limit(abs(sequence_term)**(1/sym), sym, S.Infinity)
564
+ try:
565
+ lim_evaluated = limit_seq(abs(sequence_term)**(1/sym), sym)
566
+ if lim_evaluated is not None and lim_evaluated.is_number:
567
+ if lim_evaluated < 1:
568
+ return S.true
569
+ if lim_evaluated > 1:
570
+ return S.false
571
+ except NotImplementedError:
572
+ pass
573
+
574
+ ### ------------- alternating series test ----------- ###
575
+ dict_val = sequence_term.match(S.NegativeOne**(sym + p)*q)
576
+ if not dict_val[p].has(sym) and is_decreasing(dict_val[q], interval):
577
+ return S.true
578
+
579
+ ### ------------- integral test -------------- ###
580
+ check_interval = None
581
+ from sympy.solvers.solveset import solveset
582
+ maxima = solveset(sequence_term.diff(sym), sym, interval)
583
+ if not maxima:
584
+ check_interval = interval
585
+ elif isinstance(maxima, FiniteSet) and maxima.sup.is_number:
586
+ check_interval = Interval(maxima.sup, interval.sup)
587
+ if (check_interval is not None and
588
+ (is_decreasing(sequence_term, check_interval) or
589
+ is_decreasing(-sequence_term, check_interval))):
590
+ integral_val = Integral(
591
+ sequence_term, (sym, lower_limit, upper_limit))
592
+ try:
593
+ integral_val_evaluated = integral_val.doit()
594
+ if integral_val_evaluated.is_number:
595
+ return S(integral_val_evaluated.is_finite)
596
+ except NotImplementedError:
597
+ pass
598
+
599
+ ### ----- Dirichlet and bounded times convergent tests ----- ###
600
+ # TODO
601
+ #
602
+ # Dirichlet_test
603
+ # https://en.wikipedia.org/wiki/Dirichlet%27s_test
604
+ #
605
+ # Bounded times convergent test
606
+ # It is based on comparison theorems for series.
607
+ # In particular, if the general term of a series can
608
+ # be written as a product of two terms a_n and b_n
609
+ # and if a_n is bounded and if Sum(b_n) is absolutely
610
+ # convergent, then the original series Sum(a_n * b_n)
611
+ # is absolutely convergent and so convergent.
612
+ #
613
+ # The following code can grows like 2**n where n is the
614
+ # number of args in order.expr
615
+ # Possibly combined with the potentially slow checks
616
+ # inside the loop, could make this test extremely slow
617
+ # for larger summation expressions.
618
+
619
+ if order.expr.is_Mul:
620
+ args = order.expr.args
621
+ argset = set(args)
622
+
623
+ ### -------------- Dirichlet tests -------------- ###
624
+ m = Dummy('m', integer=True)
625
+ def _dirichlet_test(g_n):
626
+ try:
627
+ ing_val = limit_seq(Sum(g_n, (sym, interval.inf, m)).doit(), m)
628
+ if ing_val is not None and ing_val.is_finite:
629
+ return S.true
630
+ except NotImplementedError:
631
+ pass
632
+
633
+ ### -------- bounded times convergent test ---------###
634
+ def _bounded_convergent_test(g1_n, g2_n):
635
+ try:
636
+ lim_val = limit_seq(g1_n, sym)
637
+ if lim_val is not None and (lim_val.is_finite or (
638
+ isinstance(lim_val, AccumulationBounds)
639
+ and (lim_val.max - lim_val.min).is_finite)):
640
+ if Sum(g2_n, (sym, lower_limit, upper_limit)).is_absolutely_convergent():
641
+ return S.true
642
+ except NotImplementedError:
643
+ pass
644
+
645
+ for n in range(1, len(argset)):
646
+ for a_tuple in itertools.combinations(args, n):
647
+ b_set = argset - set(a_tuple)
648
+ a_n = Mul(*a_tuple)
649
+ b_n = Mul(*b_set)
650
+
651
+ if is_decreasing(a_n, interval):
652
+ dirich = _dirichlet_test(b_n)
653
+ if dirich is not None:
654
+ return dirich
655
+
656
+ bc_test = _bounded_convergent_test(a_n, b_n)
657
+ if bc_test is not None:
658
+ return bc_test
659
+
660
+ _sym = self.limits[0][0]
661
+ sequence_term = sequence_term.xreplace({sym: _sym})
662
+ raise NotImplementedError("The algorithm to find the Sum convergence of %s "
663
+ "is not yet implemented" % (sequence_term))
664
+
665
+ def is_absolutely_convergent(self):
666
+ """
667
+ Checks for the absolute convergence of an infinite series.
668
+
669
+ Same as checking convergence of absolute value of sequence_term of
670
+ an infinite series.
671
+
672
+ References
673
+ ==========
674
+
675
+ .. [1] https://en.wikipedia.org/wiki/Absolute_convergence
676
+
677
+ Examples
678
+ ========
679
+
680
+ >>> from sympy import Sum, Symbol, oo
681
+ >>> n = Symbol('n', integer=True)
682
+ >>> Sum((-1)**n, (n, 1, oo)).is_absolutely_convergent()
683
+ False
684
+ >>> Sum((-1)**n/n**2, (n, 1, oo)).is_absolutely_convergent()
685
+ True
686
+
687
+ See Also
688
+ ========
689
+
690
+ Sum.is_convergent
691
+ """
692
+ return Sum(abs(self.function), self.limits).is_convergent()
693
+
694
+ def euler_maclaurin(self, m=0, n=0, eps=0, eval_integral=True):
695
+ """
696
+ Return an Euler-Maclaurin approximation of self, where m is the
697
+ number of leading terms to sum directly and n is the number of
698
+ terms in the tail.
699
+
700
+ With m = n = 0, this is simply the corresponding integral
701
+ plus a first-order endpoint correction.
702
+
703
+ Returns (s, e) where s is the Euler-Maclaurin approximation
704
+ and e is the estimated error (taken to be the magnitude of
705
+ the first omitted term in the tail):
706
+
707
+ >>> from sympy.abc import k, a, b
708
+ >>> from sympy import Sum
709
+ >>> Sum(1/k, (k, 2, 5)).doit().evalf()
710
+ 1.28333333333333
711
+ >>> s, e = Sum(1/k, (k, 2, 5)).euler_maclaurin()
712
+ >>> s
713
+ -log(2) + 7/20 + log(5)
714
+ >>> from sympy import sstr
715
+ >>> print(sstr((s.evalf(), e.evalf()), full_prec=True))
716
+ (1.26629073187415, 0.0175000000000000)
717
+
718
+ The endpoints may be symbolic:
719
+
720
+ >>> s, e = Sum(1/k, (k, a, b)).euler_maclaurin()
721
+ >>> s
722
+ -log(a) + log(b) + 1/(2*b) + 1/(2*a)
723
+ >>> e
724
+ Abs(1/(12*b**2) - 1/(12*a**2))
725
+
726
+ If the function is a polynomial of degree at most 2n+1, the
727
+ Euler-Maclaurin formula becomes exact (and e = 0 is returned):
728
+
729
+ >>> Sum(k, (k, 2, b)).euler_maclaurin()
730
+ (b**2/2 + b/2 - 1, 0)
731
+ >>> Sum(k, (k, 2, b)).doit()
732
+ b**2/2 + b/2 - 1
733
+
734
+ With a nonzero eps specified, the summation is ended
735
+ as soon as the remainder term is less than the epsilon.
736
+ """
737
+ m = int(m)
738
+ n = int(n)
739
+ f = self.function
740
+ if len(self.limits) != 1:
741
+ raise ValueError("More than 1 limit")
742
+ i, a, b = self.limits[0]
743
+ if (a > b) == True:
744
+ if a - b == 1:
745
+ return S.Zero, S.Zero
746
+ a, b = b + 1, a - 1
747
+ f = -f
748
+ s = S.Zero
749
+ if m:
750
+ if b.is_Integer and a.is_Integer:
751
+ m = min(m, b - a + 1)
752
+ if not eps or f.is_polynomial(i):
753
+ s = Add(*[f.subs(i, a + k) for k in range(m)])
754
+ else:
755
+ term = f.subs(i, a)
756
+ if term:
757
+ test = abs(term.evalf(3)) < eps
758
+ if test == True:
759
+ return s, abs(term)
760
+ elif not (test == False):
761
+ # a symbolic Relational class, can't go further
762
+ return term, S.Zero
763
+ s = term
764
+ for k in range(1, m):
765
+ term = f.subs(i, a + k)
766
+ if abs(term.evalf(3)) < eps and term != 0:
767
+ return s, abs(term)
768
+ s += term
769
+ if b - a + 1 == m:
770
+ return s, S.Zero
771
+ a += m
772
+ x = Dummy('x')
773
+ I = Integral(f.subs(i, x), (x, a, b))
774
+ if eval_integral:
775
+ I = I.doit()
776
+ s += I
777
+
778
+ def fpoint(expr):
779
+ if b is S.Infinity:
780
+ return expr.subs(i, a), 0
781
+ return expr.subs(i, a), expr.subs(i, b)
782
+ fa, fb = fpoint(f)
783
+ iterm = (fa + fb)/2
784
+ g = f.diff(i)
785
+ for k in range(1, n + 2):
786
+ ga, gb = fpoint(g)
787
+ term = bernoulli(2*k)/factorial(2*k)*(gb - ga)
788
+ if k > n:
789
+ break
790
+ if eps and term:
791
+ term_evalf = term.evalf(3)
792
+ if term_evalf is S.NaN:
793
+ return S.NaN, S.NaN
794
+ if abs(term_evalf) < eps:
795
+ break
796
+ s += term
797
+ g = g.diff(i, 2, simplify=False)
798
+ return s + iterm, abs(term)
799
+
800
+
801
+ def reverse_order(self, *indices):
802
+ """
803
+ Reverse the order of a limit in a Sum.
804
+
805
+ Explanation
806
+ ===========
807
+
808
+ ``reverse_order(self, *indices)`` reverses some limits in the expression
809
+ ``self`` which can be either a ``Sum`` or a ``Product``. The selectors in
810
+ the argument ``indices`` specify some indices whose limits get reversed.
811
+ These selectors are either variable names or numerical indices counted
812
+ starting from the inner-most limit tuple.
813
+
814
+ Examples
815
+ ========
816
+
817
+ >>> from sympy import Sum
818
+ >>> from sympy.abc import x, y, a, b, c, d
819
+
820
+ >>> Sum(x, (x, 0, 3)).reverse_order(x)
821
+ Sum(-x, (x, 4, -1))
822
+ >>> Sum(x*y, (x, 1, 5), (y, 0, 6)).reverse_order(x, y)
823
+ Sum(x*y, (x, 6, 0), (y, 7, -1))
824
+ >>> Sum(x, (x, a, b)).reverse_order(x)
825
+ Sum(-x, (x, b + 1, a - 1))
826
+ >>> Sum(x, (x, a, b)).reverse_order(0)
827
+ Sum(-x, (x, b + 1, a - 1))
828
+
829
+ While one should prefer variable names when specifying which limits
830
+ to reverse, the index counting notation comes in handy in case there
831
+ are several symbols with the same name.
832
+
833
+ >>> S = Sum(x**2, (x, a, b), (x, c, d))
834
+ >>> S
835
+ Sum(x**2, (x, a, b), (x, c, d))
836
+ >>> S0 = S.reverse_order(0)
837
+ >>> S0
838
+ Sum(-x**2, (x, b + 1, a - 1), (x, c, d))
839
+ >>> S1 = S0.reverse_order(1)
840
+ >>> S1
841
+ Sum(x**2, (x, b + 1, a - 1), (x, d + 1, c - 1))
842
+
843
+ Of course we can mix both notations:
844
+
845
+ >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(x, 1)
846
+ Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
847
+ >>> Sum(x*y, (x, a, b), (y, 2, 5)).reverse_order(y, x)
848
+ Sum(x*y, (x, b + 1, a - 1), (y, 6, 1))
849
+
850
+ See Also
851
+ ========
852
+
853
+ sympy.concrete.expr_with_intlimits.ExprWithIntLimits.index, reorder_limit,
854
+ sympy.concrete.expr_with_intlimits.ExprWithIntLimits.reorder
855
+
856
+ References
857
+ ==========
858
+
859
+ .. [1] Michael Karr, "Summation in Finite Terms", Journal of the ACM,
860
+ Volume 28 Issue 2, April 1981, Pages 305-350
861
+ https://dl.acm.org/doi/10.1145/322248.322255
862
+ """
863
+ l_indices = list(indices)
864
+
865
+ for i, indx in enumerate(l_indices):
866
+ if not isinstance(indx, int):
867
+ l_indices[i] = self.index(indx)
868
+
869
+ e = 1
870
+ limits = []
871
+ for i, limit in enumerate(self.limits):
872
+ l = limit
873
+ if i in l_indices:
874
+ e = -e
875
+ l = (limit[0], limit[2] + 1, limit[1] - 1)
876
+ limits.append(l)
877
+
878
+ return Sum(e * self.function, *limits)
879
+
880
+ def _eval_rewrite_as_Product(self, *args, **kwargs):
881
+ from sympy.concrete.products import Product
882
+ if self.function.is_extended_real:
883
+ return log(Product(exp(self.function), *self.limits))
884
+
885
+
886
+ def summation(f, *symbols, **kwargs):
887
+ r"""
888
+ Compute the summation of f with respect to symbols.
889
+
890
+ Explanation
891
+ ===========
892
+
893
+ The notation for symbols is similar to the notation used in Integral.
894
+ summation(f, (i, a, b)) computes the sum of f with respect to i from a to b,
895
+ i.e.,
896
+
897
+ ::
898
+
899
+ b
900
+ ____
901
+ \ `
902
+ summation(f, (i, a, b)) = ) f
903
+ /___,
904
+ i = a
905
+
906
+ If it cannot compute the sum, it returns an unevaluated Sum object.
907
+ Repeated sums can be computed by introducing additional symbols tuples::
908
+
909
+ Examples
910
+ ========
911
+
912
+ >>> from sympy import summation, oo, symbols, log
913
+ >>> i, n, m = symbols('i n m', integer=True)
914
+
915
+ >>> summation(2*i - 1, (i, 1, n))
916
+ n**2
917
+ >>> summation(1/2**i, (i, 0, oo))
918
+ 2
919
+ >>> summation(1/log(n)**n, (n, 2, oo))
920
+ Sum(log(n)**(-n), (n, 2, oo))
921
+ >>> summation(i, (i, 0, n), (n, 0, m))
922
+ m**3/6 + m**2/2 + m/3
923
+
924
+ >>> from sympy.abc import x
925
+ >>> from sympy import factorial
926
+ >>> summation(x**n/factorial(n), (n, 0, oo))
927
+ exp(x)
928
+
929
+ See Also
930
+ ========
931
+
932
+ Sum
933
+ Product, sympy.concrete.products.product
934
+
935
+ """
936
+ return Sum(f, *symbols, **kwargs).doit(deep=False)
937
+
938
+
939
+ def telescopic_direct(L, R, n, limits):
940
+ """
941
+ Returns the direct summation of the terms of a telescopic sum
942
+
943
+ Explanation
944
+ ===========
945
+
946
+ L is the term with lower index
947
+ R is the term with higher index
948
+ n difference between the indexes of L and R
949
+
950
+ Examples
951
+ ========
952
+
953
+ >>> from sympy.concrete.summations import telescopic_direct
954
+ >>> from sympy.abc import k, a, b
955
+ >>> telescopic_direct(1/k, -1/(k+2), 2, (k, a, b))
956
+ -1/(b + 2) - 1/(b + 1) + 1/(a + 1) + 1/a
957
+
958
+ """
959
+ (i, a, b) = limits
960
+ return Add(*[L.subs(i, a + m) + R.subs(i, b - m) for m in range(n)])
961
+
962
+
963
+ def telescopic(L, R, limits):
964
+ '''
965
+ Tries to perform the summation using the telescopic property.
966
+
967
+ Return None if not possible.
968
+ '''
969
+ (i, a, b) = limits
970
+ if L.is_Add or R.is_Add:
971
+ return None
972
+
973
+ # We want to solve(L.subs(i, i + m) + R, m)
974
+ # First we try a simple match since this does things that
975
+ # solve doesn't do, e.g. solve(cos(k+m)-cos(k), m) gives
976
+ # a more complicated solution than m == 0.
977
+
978
+ k = Wild("k")
979
+ sol = (-R).match(L.subs(i, i + k))
980
+ s = None
981
+ if sol and k in sol:
982
+ s = sol[k]
983
+ if not (s.is_Integer and L.subs(i, i + s) + R == 0):
984
+ # invalid match or match didn't work
985
+ s = None
986
+
987
+ # But there are things that match doesn't do that solve
988
+ # can do, e.g. determine that 1/(x + m) = 1/(1 - x) when m = 1
989
+
990
+ if s is None:
991
+ m = Dummy('m')
992
+ try:
993
+ from sympy.solvers.solvers import solve
994
+ sol = solve(L.subs(i, i + m) + R, m) or []
995
+ except NotImplementedError:
996
+ return None
997
+ sol = [si for si in sol if si.is_Integer and
998
+ (L.subs(i, i + si) + R).expand().is_zero]
999
+ if len(sol) != 1:
1000
+ return None
1001
+ s = sol[0]
1002
+
1003
+ if s < 0:
1004
+ return telescopic_direct(R, L, abs(s), (i, a, b))
1005
+ elif s > 0:
1006
+ return telescopic_direct(L, R, s, (i, a, b))
1007
+
1008
+
1009
+ def eval_sum(f, limits):
1010
+ (i, a, b) = limits
1011
+ if f.is_zero:
1012
+ return S.Zero
1013
+ if i not in f.free_symbols:
1014
+ return f*(b - a + 1)
1015
+ if a == b:
1016
+ return f.subs(i, a)
1017
+ if isinstance(f, Piecewise):
1018
+ if not any(i in arg.args[1].free_symbols for arg in f.args):
1019
+ # Piecewise conditions do not depend on the dummy summation variable,
1020
+ # therefore we can fold: Sum(Piecewise((e, c), ...), limits)
1021
+ # --> Piecewise((Sum(e, limits), c), ...)
1022
+ newargs = []
1023
+ for arg in f.args:
1024
+ newexpr = eval_sum(arg.expr, limits)
1025
+ if newexpr is None:
1026
+ return None
1027
+ newargs.append((newexpr, arg.cond))
1028
+ return f.func(*newargs)
1029
+
1030
+ if f.has(KroneckerDelta):
1031
+ from .delta import deltasummation, _has_simple_delta
1032
+ f = f.replace(
1033
+ lambda x: isinstance(x, Sum),
1034
+ lambda x: x.factor()
1035
+ )
1036
+ if _has_simple_delta(f, limits[0]):
1037
+ return deltasummation(f, limits)
1038
+
1039
+ dif = b - a
1040
+ definite = dif.is_Integer
1041
+ # Doing it directly may be faster if there are very few terms.
1042
+ if definite and (dif < 100):
1043
+ return eval_sum_direct(f, (i, a, b))
1044
+ if isinstance(f, Piecewise):
1045
+ return None
1046
+ # Try to do it symbolically. Even when the number of terms is
1047
+ # known, this can save time when b-a is big.
1048
+ value = eval_sum_symbolic(f.expand(), (i, a, b))
1049
+ if value is not None:
1050
+ return value
1051
+ # Do it directly
1052
+ if definite:
1053
+ return eval_sum_direct(f, (i, a, b))
1054
+
1055
+
1056
+ def eval_sum_direct(expr, limits):
1057
+ """
1058
+ Evaluate expression directly, but perform some simple checks first
1059
+ to possibly result in a smaller expression and faster execution.
1060
+ """
1061
+ (i, a, b) = limits
1062
+
1063
+ dif = b - a
1064
+ # Linearity
1065
+ if expr.is_Mul:
1066
+ # Try factor out everything not including i
1067
+ without_i, with_i = expr.as_independent(i)
1068
+ if without_i != 1:
1069
+ s = eval_sum_direct(with_i, (i, a, b))
1070
+ if s:
1071
+ r = without_i*s
1072
+ if r is not S.NaN:
1073
+ return r
1074
+ else:
1075
+ # Try term by term
1076
+ L, R = expr.as_two_terms()
1077
+
1078
+ if not L.has(i):
1079
+ sR = eval_sum_direct(R, (i, a, b))
1080
+ if sR:
1081
+ return L*sR
1082
+
1083
+ if not R.has(i):
1084
+ sL = eval_sum_direct(L, (i, a, b))
1085
+ if sL:
1086
+ return sL*R
1087
+
1088
+ # do this whether its an Add or Mul
1089
+ # e.g. apart(1/(25*i**2 + 45*i + 14)) and
1090
+ # apart(1/((5*i + 2)*(5*i + 7))) ->
1091
+ # -1/(5*(5*i + 7)) + 1/(5*(5*i + 2))
1092
+ try:
1093
+ expr = apart(expr, i) # see if it becomes an Add
1094
+ except PolynomialError:
1095
+ pass
1096
+
1097
+ if expr.is_Add:
1098
+ # Try factor out everything not including i
1099
+ without_i, with_i = expr.as_independent(i)
1100
+ if without_i != 0:
1101
+ s = eval_sum_direct(with_i, (i, a, b))
1102
+ if s:
1103
+ r = without_i*(dif + 1) + s
1104
+ if r is not S.NaN:
1105
+ return r
1106
+ else:
1107
+ # Try term by term
1108
+ L, R = expr.as_two_terms()
1109
+ lsum = eval_sum_direct(L, (i, a, b))
1110
+ rsum = eval_sum_direct(R, (i, a, b))
1111
+
1112
+ if None not in (lsum, rsum):
1113
+ r = lsum + rsum
1114
+ if r is not S.NaN:
1115
+ return r
1116
+
1117
+ return Add(*[expr.subs(i, a + j) for j in range(dif + 1)])
1118
+
1119
+
1120
+ def eval_sum_symbolic(f, limits):
1121
+ f_orig = f
1122
+ (i, a, b) = limits
1123
+ if not f.has(i):
1124
+ return f*(b - a + 1)
1125
+
1126
+ # Linearity
1127
+ if f.is_Mul:
1128
+ # Try factor out everything not including i
1129
+ without_i, with_i = f.as_independent(i)
1130
+ if without_i != 1:
1131
+ s = eval_sum_symbolic(with_i, (i, a, b))
1132
+ if s:
1133
+ r = without_i*s
1134
+ if r is not S.NaN:
1135
+ return r
1136
+ else:
1137
+ # Try term by term
1138
+ L, R = f.as_two_terms()
1139
+
1140
+ if not L.has(i):
1141
+ sR = eval_sum_symbolic(R, (i, a, b))
1142
+ if sR:
1143
+ return L*sR
1144
+
1145
+ if not R.has(i):
1146
+ sL = eval_sum_symbolic(L, (i, a, b))
1147
+ if sL:
1148
+ return sL*R
1149
+
1150
+ # do this whether its an Add or Mul
1151
+ # e.g. apart(1/(25*i**2 + 45*i + 14)) and
1152
+ # apart(1/((5*i + 2)*(5*i + 7))) ->
1153
+ # -1/(5*(5*i + 7)) + 1/(5*(5*i + 2))
1154
+ try:
1155
+ f = apart(f, i)
1156
+ except PolynomialError:
1157
+ pass
1158
+
1159
+ if f.is_Add:
1160
+ L, R = f.as_two_terms()
1161
+ lrsum = telescopic(L, R, (i, a, b))
1162
+
1163
+ if lrsum:
1164
+ return lrsum
1165
+
1166
+ # Try factor out everything not including i
1167
+ without_i, with_i = f.as_independent(i)
1168
+ if without_i != 0:
1169
+ s = eval_sum_symbolic(with_i, (i, a, b))
1170
+ if s:
1171
+ r = without_i*(b - a + 1) + s
1172
+ if r is not S.NaN:
1173
+ return r
1174
+ else:
1175
+ # Try term by term
1176
+ lsum = eval_sum_symbolic(L, (i, a, b))
1177
+ rsum = eval_sum_symbolic(R, (i, a, b))
1178
+
1179
+ if None not in (lsum, rsum):
1180
+ r = lsum + rsum
1181
+ if r is not S.NaN:
1182
+ return r
1183
+
1184
+
1185
+ # Polynomial terms with Faulhaber's formula
1186
+ n = Wild('n')
1187
+ result = f.match(i**n)
1188
+
1189
+ if result is not None:
1190
+ n = result[n]
1191
+
1192
+ if n.is_Integer:
1193
+ if n >= 0:
1194
+ if (b is S.Infinity and a is not S.NegativeInfinity) or \
1195
+ (a is S.NegativeInfinity and b is not S.Infinity):
1196
+ return S.Infinity
1197
+ return ((bernoulli(n + 1, b + 1) - bernoulli(n + 1, a))/(n + 1)).expand()
1198
+ elif a.is_Integer and a >= 1:
1199
+ if n == -1:
1200
+ return harmonic(b) - harmonic(a - 1)
1201
+ else:
1202
+ return harmonic(b, abs(n)) - harmonic(a - 1, abs(n))
1203
+
1204
+ if not (a.has(S.Infinity, S.NegativeInfinity) or
1205
+ b.has(S.Infinity, S.NegativeInfinity)):
1206
+ # Geometric terms
1207
+ c1 = Wild('c1', exclude=[i])
1208
+ c2 = Wild('c2', exclude=[i])
1209
+ c3 = Wild('c3', exclude=[i])
1210
+ wexp = Wild('wexp')
1211
+
1212
+ # Here we first attempt powsimp on f for easier matching with the
1213
+ # exponential pattern, and attempt expansion on the exponent for easier
1214
+ # matching with the linear pattern.
1215
+ e = f.powsimp().match(c1 ** wexp)
1216
+ if e is not None:
1217
+ e_exp = e.pop(wexp).expand().match(c2*i + c3)
1218
+ if e_exp is not None:
1219
+ e.update(e_exp)
1220
+
1221
+ p = (c1**c3).subs(e)
1222
+ q = (c1**c2).subs(e)
1223
+ r = p*(q**a - q**(b + 1))/(1 - q)
1224
+ l = p*(b - a + 1)
1225
+ return Piecewise((l, Eq(q, S.One)), (r, True))
1226
+
1227
+ r = gosper_sum(f, (i, a, b))
1228
+
1229
+ if isinstance(r, (Mul,Add)):
1230
+ from sympy.simplify.radsimp import denom
1231
+ from sympy.solvers.solvers import solve
1232
+ non_limit = r.free_symbols - Tuple(*limits[1:]).free_symbols
1233
+ den = denom(together(r))
1234
+ den_sym = non_limit & den.free_symbols
1235
+ args = []
1236
+ for v in ordered(den_sym):
1237
+ try:
1238
+ s = solve(den, v)
1239
+ m = Eq(v, s[0]) if s else S.false
1240
+ if m != False:
1241
+ args.append((Sum(f_orig.subs(*m.args), limits).doit(), m))
1242
+ break
1243
+ except NotImplementedError:
1244
+ continue
1245
+
1246
+ args.append((r, True))
1247
+ return Piecewise(*args)
1248
+
1249
+ if r not in (None, S.NaN):
1250
+ return r
1251
+
1252
+ h = eval_sum_hyper(f_orig, (i, a, b))
1253
+ if h is not None:
1254
+ return h
1255
+
1256
+ r = eval_sum_residue(f_orig, (i, a, b))
1257
+ if r is not None:
1258
+ return r
1259
+
1260
+ factored = f_orig.factor()
1261
+ if factored != f_orig:
1262
+ return eval_sum_symbolic(factored, (i, a, b))
1263
+
1264
+
1265
+ def _eval_sum_hyper(f, i, a):
1266
+ """ Returns (res, cond). Sums from a to oo. """
1267
+ if a != 0:
1268
+ return _eval_sum_hyper(f.subs(i, i + a), i, 0)
1269
+
1270
+ if f.subs(i, 0) == 0:
1271
+ from sympy.simplify.simplify import simplify
1272
+ if simplify(f.subs(i, Dummy('i', integer=True, positive=True))) == 0:
1273
+ return S.Zero, True
1274
+ return _eval_sum_hyper(f.subs(i, i + 1), i, 0)
1275
+
1276
+ from sympy.simplify.simplify import hypersimp
1277
+ hs = hypersimp(f, i)
1278
+ if hs is None:
1279
+ return None
1280
+
1281
+ if isinstance(hs, Float):
1282
+ from sympy.simplify.simplify import nsimplify
1283
+ hs = nsimplify(hs)
1284
+
1285
+ from sympy.simplify.combsimp import combsimp
1286
+ from sympy.simplify.hyperexpand import hyperexpand
1287
+ from sympy.simplify.radsimp import fraction
1288
+ numer, denom = fraction(factor(hs))
1289
+ top, topl = numer.as_coeff_mul(i)
1290
+ bot, botl = denom.as_coeff_mul(i)
1291
+ ab = [top, bot]
1292
+ factors = [topl, botl]
1293
+ params = [[], []]
1294
+ for k in range(2):
1295
+ for fac in factors[k]:
1296
+ mul = 1
1297
+ if fac.is_Pow:
1298
+ mul = fac.exp
1299
+ fac = fac.base
1300
+ if not mul.is_Integer:
1301
+ return None
1302
+ p = Poly(fac, i)
1303
+ if p.degree() != 1:
1304
+ return None
1305
+ m, n = p.all_coeffs()
1306
+ ab[k] *= m**mul
1307
+ params[k] += [n/m]*mul
1308
+
1309
+ # Add "1" to numerator parameters, to account for implicit n! in
1310
+ # hypergeometric series.
1311
+ ap = params[0] + [1]
1312
+ bq = params[1]
1313
+ x = ab[0]/ab[1]
1314
+ h = hyper(ap, bq, x)
1315
+ f = combsimp(f)
1316
+ return f.subs(i, 0)*hyperexpand(h), h.convergence_statement
1317
+
1318
+
1319
+ def eval_sum_hyper(f, i_a_b):
1320
+ i, a, b = i_a_b
1321
+
1322
+ if f.is_hypergeometric(i) is False:
1323
+ return
1324
+
1325
+ if (b - a).is_Integer:
1326
+ # We are never going to do better than doing the sum in the obvious way
1327
+ return None
1328
+
1329
+ old_sum = Sum(f, (i, a, b))
1330
+
1331
+ if b != S.Infinity:
1332
+ if a is S.NegativeInfinity:
1333
+ res = _eval_sum_hyper(f.subs(i, -i), i, -b)
1334
+ if res is not None:
1335
+ return Piecewise(res, (old_sum, True))
1336
+ else:
1337
+ n_illegal = lambda x: sum(x.count(_) for _ in _illegal)
1338
+ had = n_illegal(f)
1339
+ # check that no extra illegals are introduced
1340
+ res1 = _eval_sum_hyper(f, i, a)
1341
+ if res1 is None or n_illegal(res1) > had:
1342
+ return
1343
+ res2 = _eval_sum_hyper(f, i, b + 1)
1344
+ if res2 is None or n_illegal(res2) > had:
1345
+ return
1346
+ (res1, cond1), (res2, cond2) = res1, res2
1347
+ cond = And(cond1, cond2)
1348
+ if cond == False:
1349
+ return None
1350
+ return Piecewise((res1 - res2, cond), (old_sum, True))
1351
+
1352
+ if a is S.NegativeInfinity:
1353
+ res1 = _eval_sum_hyper(f.subs(i, -i), i, 1)
1354
+ res2 = _eval_sum_hyper(f, i, 0)
1355
+ if res1 is None or res2 is None:
1356
+ return None
1357
+ res1, cond1 = res1
1358
+ res2, cond2 = res2
1359
+ cond = And(cond1, cond2)
1360
+ if cond == False or cond.as_set() == S.EmptySet:
1361
+ return None
1362
+ return Piecewise((res1 + res2, cond), (old_sum, True))
1363
+
1364
+ # Now b == oo, a != -oo
1365
+ res = _eval_sum_hyper(f, i, a)
1366
+ if res is not None:
1367
+ r, c = res
1368
+ if c == False:
1369
+ if r.is_number:
1370
+ f = f.subs(i, Dummy('i', integer=True, positive=True) + a)
1371
+ if f.is_positive or f.is_zero:
1372
+ return S.Infinity
1373
+ elif f.is_negative:
1374
+ return S.NegativeInfinity
1375
+ return None
1376
+ return Piecewise(res, (old_sum, True))
1377
+
1378
+
1379
+ def eval_sum_residue(f, i_a_b):
1380
+ r"""Compute the infinite summation with residues
1381
+
1382
+ Notes
1383
+ =====
1384
+
1385
+ If $f(n), g(n)$ are polynomials with $\deg(g(n)) - \deg(f(n)) \ge 2$,
1386
+ some infinite summations can be computed by the following residue
1387
+ evaluations.
1388
+
1389
+ .. math::
1390
+ \sum_{n=-\infty, g(n) \ne 0}^{\infty} \frac{f(n)}{g(n)} =
1391
+ -\pi \sum_{\alpha|g(\alpha)=0}
1392
+ \text{Res}(\cot(\pi x) \frac{f(x)}{g(x)}, \alpha)
1393
+
1394
+ .. math::
1395
+ \sum_{n=-\infty, g(n) \ne 0}^{\infty} (-1)^n \frac{f(n)}{g(n)} =
1396
+ -\pi \sum_{\alpha|g(\alpha)=0}
1397
+ \text{Res}(\csc(\pi x) \frac{f(x)}{g(x)}, \alpha)
1398
+
1399
+ Examples
1400
+ ========
1401
+
1402
+ >>> from sympy import Sum, oo, Symbol
1403
+ >>> x = Symbol('x')
1404
+
1405
+ Doubly infinite series of rational functions.
1406
+
1407
+ >>> Sum(1 / (x**2 + 1), (x, -oo, oo)).doit()
1408
+ pi/tanh(pi)
1409
+
1410
+ Doubly infinite alternating series of rational functions.
1411
+
1412
+ >>> Sum((-1)**x / (x**2 + 1), (x, -oo, oo)).doit()
1413
+ pi/sinh(pi)
1414
+
1415
+ Infinite series of even rational functions.
1416
+
1417
+ >>> Sum(1 / (x**2 + 1), (x, 0, oo)).doit()
1418
+ 1/2 + pi/(2*tanh(pi))
1419
+
1420
+ Infinite series of alternating even rational functions.
1421
+
1422
+ >>> Sum((-1)**x / (x**2 + 1), (x, 0, oo)).doit()
1423
+ pi/(2*sinh(pi)) + 1/2
1424
+
1425
+ This also have heuristics to transform arbitrarily shifted summand or
1426
+ arbitrarily shifted summation range to the canonical problem the
1427
+ formula can handle.
1428
+
1429
+ >>> Sum(1 / (x**2 + 2*x + 2), (x, -1, oo)).doit()
1430
+ 1/2 + pi/(2*tanh(pi))
1431
+ >>> Sum(1 / (x**2 + 4*x + 5), (x, -2, oo)).doit()
1432
+ 1/2 + pi/(2*tanh(pi))
1433
+ >>> Sum(1 / (x**2 + 1), (x, 1, oo)).doit()
1434
+ -1/2 + pi/(2*tanh(pi))
1435
+ >>> Sum(1 / (x**2 + 1), (x, 2, oo)).doit()
1436
+ -1 + pi/(2*tanh(pi))
1437
+
1438
+ References
1439
+ ==========
1440
+
1441
+ .. [#] http://www.supermath.info/InfiniteSeriesandtheResidueTheorem.pdf
1442
+
1443
+ .. [#] Asmar N.H., Grafakos L. (2018) Residue Theory.
1444
+ In: Complex Analysis with Applications.
1445
+ Undergraduate Texts in Mathematics. Springer, Cham.
1446
+ https://doi.org/10.1007/978-3-319-94063-2_5
1447
+ """
1448
+ i, a, b = i_a_b
1449
+
1450
+ def is_even_function(numer, denom):
1451
+ """Test if the rational function is an even function"""
1452
+ numer_even = all(i % 2 == 0 for (i,) in numer.monoms())
1453
+ denom_even = all(i % 2 == 0 for (i,) in denom.monoms())
1454
+ numer_odd = all(i % 2 == 1 for (i,) in numer.monoms())
1455
+ denom_odd = all(i % 2 == 1 for (i,) in denom.monoms())
1456
+ return (numer_even and denom_even) or (numer_odd and denom_odd)
1457
+
1458
+ def match_rational(f, i):
1459
+ numer, denom = f.as_numer_denom()
1460
+ try:
1461
+ (numer, denom), opt = parallel_poly_from_expr((numer, denom), i)
1462
+ except (PolificationFailed, PolynomialError):
1463
+ return None
1464
+ return numer, denom
1465
+
1466
+ def get_poles(denom):
1467
+ roots = denom.sqf_part().all_roots()
1468
+ roots = sift(roots, lambda x: x.is_integer)
1469
+ if None in roots:
1470
+ return None
1471
+ int_roots, nonint_roots = roots[True], roots[False]
1472
+ return int_roots, nonint_roots
1473
+
1474
+ def get_shift(denom):
1475
+ n = denom.degree(i)
1476
+ a = denom.coeff_monomial(i**n)
1477
+ b = denom.coeff_monomial(i**(n-1))
1478
+ shift = - b / a / n
1479
+ return shift
1480
+
1481
+ #Need a dummy symbol with no assumptions set for get_residue_factor
1482
+ z = Dummy('z')
1483
+
1484
+ def get_residue_factor(numer, denom, alternating):
1485
+ residue_factor = (numer.as_expr() / denom.as_expr()).subs(i, z)
1486
+ if not alternating:
1487
+ residue_factor *= cot(S.Pi * z)
1488
+ else:
1489
+ residue_factor *= csc(S.Pi * z)
1490
+ return residue_factor
1491
+
1492
+ # We don't know how to deal with symbolic constants in summand
1493
+ if f.free_symbols - {i}:
1494
+ return None
1495
+
1496
+ if not (a.is_Integer or a in (S.Infinity, S.NegativeInfinity)):
1497
+ return None
1498
+ if not (b.is_Integer or b in (S.Infinity, S.NegativeInfinity)):
1499
+ return None
1500
+
1501
+ # Quick exit heuristic for the sums which doesn't have infinite range
1502
+ if a != S.NegativeInfinity and b != S.Infinity:
1503
+ return None
1504
+
1505
+ match = match_rational(f, i)
1506
+ if match:
1507
+ alternating = False
1508
+ numer, denom = match
1509
+ else:
1510
+ match = match_rational(f / S.NegativeOne**i, i)
1511
+ if match:
1512
+ alternating = True
1513
+ numer, denom = match
1514
+ else:
1515
+ return None
1516
+
1517
+ if denom.degree(i) - numer.degree(i) < 2:
1518
+ return None
1519
+
1520
+ if (a, b) == (S.NegativeInfinity, S.Infinity):
1521
+ poles = get_poles(denom)
1522
+ if poles is None:
1523
+ return None
1524
+ int_roots, nonint_roots = poles
1525
+
1526
+ if int_roots:
1527
+ return None
1528
+
1529
+ residue_factor = get_residue_factor(numer, denom, alternating)
1530
+ residues = [residue(residue_factor, z, root) for root in nonint_roots]
1531
+ return -S.Pi * sum(residues)
1532
+
1533
+ if not (a.is_finite and b is S.Infinity):
1534
+ return None
1535
+
1536
+ if not is_even_function(numer, denom):
1537
+ # Try shifting summation and check if the summand can be made
1538
+ # and even function from the origin.
1539
+ # Sum(f(n), (n, a, b)) => Sum(f(n + s), (n, a - s, b - s))
1540
+ shift = get_shift(denom)
1541
+
1542
+ if not shift.is_Integer:
1543
+ return None
1544
+ if shift == 0:
1545
+ return None
1546
+
1547
+ numer = numer.shift(shift)
1548
+ denom = denom.shift(shift)
1549
+
1550
+ if not is_even_function(numer, denom):
1551
+ return None
1552
+
1553
+ if alternating:
1554
+ f = S.NegativeOne**i * (S.NegativeOne**shift * numer.as_expr() / denom.as_expr())
1555
+ else:
1556
+ f = numer.as_expr() / denom.as_expr()
1557
+ return eval_sum_residue(f, (i, a-shift, b-shift))
1558
+
1559
+ poles = get_poles(denom)
1560
+ if poles is None:
1561
+ return None
1562
+ int_roots, nonint_roots = poles
1563
+
1564
+ if int_roots:
1565
+ int_roots = [int(root) for root in int_roots]
1566
+ int_roots_max = max(int_roots)
1567
+ int_roots_min = min(int_roots)
1568
+ # Integer valued poles must be next to each other
1569
+ # and also symmetric from origin (Because the function is even)
1570
+ if not len(int_roots) == int_roots_max - int_roots_min + 1:
1571
+ return None
1572
+
1573
+ # Check whether the summation indices contain poles
1574
+ if a <= max(int_roots):
1575
+ return None
1576
+
1577
+ residue_factor = get_residue_factor(numer, denom, alternating)
1578
+ residues = [residue(residue_factor, z, root) for root in int_roots + nonint_roots]
1579
+ full_sum = -S.Pi * sum(residues)
1580
+
1581
+ if not int_roots:
1582
+ # Compute Sum(f, (i, 0, oo)) by adding a extraneous evaluation
1583
+ # at the origin.
1584
+ half_sum = (full_sum + f.xreplace({i: 0})) / 2
1585
+
1586
+ # Add and subtract extraneous evaluations
1587
+ extraneous_neg = [f.xreplace({i: i0}) for i0 in range(int(a), 0)]
1588
+ extraneous_pos = [f.xreplace({i: i0}) for i0 in range(0, int(a))]
1589
+ result = half_sum + sum(extraneous_neg) - sum(extraneous_pos)
1590
+
1591
+ return result
1592
+
1593
+ # Compute Sum(f, (i, min(poles) + 1, oo))
1594
+ half_sum = full_sum / 2
1595
+
1596
+ # Subtract extraneous evaluations
1597
+ extraneous = [f.xreplace({i: i0}) for i0 in range(max(int_roots) + 1, int(a))]
1598
+ result = half_sum - sum(extraneous)
1599
+
1600
+ return result
1601
+
1602
+
1603
+ def _eval_matrix_sum(expression):
1604
+ f = expression.function
1605
+ for limit in expression.limits:
1606
+ i, a, b = limit
1607
+ dif = b - a
1608
+ if dif.is_Integer:
1609
+ if (dif < 0) == True:
1610
+ a, b = b + 1, a - 1
1611
+ f = -f
1612
+
1613
+ newf = eval_sum_direct(f, (i, a, b))
1614
+ if newf is not None:
1615
+ return newf.doit()
1616
+
1617
+
1618
+ def _dummy_with_inherited_properties_concrete(limits):
1619
+ """
1620
+ Return a Dummy symbol that inherits as many assumptions as possible
1621
+ from the provided symbol and limits.
1622
+
1623
+ If the symbol already has all True assumption shared by the limits
1624
+ then return None.
1625
+ """
1626
+ x, a, b = limits
1627
+ l = [a, b]
1628
+
1629
+ assumptions_to_consider = ['extended_nonnegative', 'nonnegative',
1630
+ 'extended_nonpositive', 'nonpositive',
1631
+ 'extended_positive', 'positive',
1632
+ 'extended_negative', 'negative',
1633
+ 'integer', 'rational', 'finite',
1634
+ 'zero', 'real', 'extended_real']
1635
+
1636
+ assumptions_to_keep = {}
1637
+ assumptions_to_add = {}
1638
+ for assum in assumptions_to_consider:
1639
+ assum_true = x._assumptions.get(assum, None)
1640
+ if assum_true:
1641
+ assumptions_to_keep[assum] = True
1642
+ elif all(getattr(i, 'is_' + assum) for i in l):
1643
+ assumptions_to_add[assum] = True
1644
+ if assumptions_to_add:
1645
+ assumptions_to_keep.update(assumptions_to_add)
1646
+ return Dummy('d', **assumptions_to_keep)
lib/python3.12/site-packages/sympy/concrete/tests/__init__.py ADDED
File without changes
lib/python3.12/site-packages/sympy/concrete/tests/__pycache__/__init__.cpython-312.pyc ADDED
Binary file (199 Bytes). View file