Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- lib/python3.12/site-packages/sympy/algebras/__init__.py +3 -0
- lib/python3.12/site-packages/sympy/algebras/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/algebras/__pycache__/quaternion.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/algebras/quaternion.py +1667 -0
- lib/python3.12/site-packages/sympy/algebras/tests/__init__.py +0 -0
- lib/python3.12/site-packages/sympy/algebras/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/algebras/tests/__pycache__/test_quaternion.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/algebras/tests/test_quaternion.py +428 -0
- lib/python3.12/site-packages/sympy/calculus/__init__.py +25 -0
- lib/python3.12/site-packages/sympy/calculus/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/__pycache__/accumulationbounds.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/__pycache__/euler.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/__pycache__/finite_diff.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/__pycache__/singularities.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/__pycache__/util.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/accumulationbounds.py +804 -0
- lib/python3.12/site-packages/sympy/calculus/euler.py +108 -0
- lib/python3.12/site-packages/sympy/calculus/finite_diff.py +476 -0
- lib/python3.12/site-packages/sympy/calculus/singularities.py +406 -0
- lib/python3.12/site-packages/sympy/calculus/tests/__init__.py +0 -0
- lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_accumulationbounds.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_euler.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_finite_diff.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_singularities.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/tests/__pycache__/test_util.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/calculus/tests/test_accumulationbounds.py +336 -0
- lib/python3.12/site-packages/sympy/calculus/tests/test_euler.py +74 -0
- lib/python3.12/site-packages/sympy/calculus/tests/test_finite_diff.py +168 -0
- lib/python3.12/site-packages/sympy/calculus/tests/test_singularities.py +122 -0
- lib/python3.12/site-packages/sympy/calculus/tests/test_util.py +392 -0
- lib/python3.12/site-packages/sympy/calculus/util.py +905 -0
- lib/python3.12/site-packages/sympy/concrete/__init__.py +8 -0
- lib/python3.12/site-packages/sympy/concrete/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/concrete/__pycache__/delta.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/concrete/__pycache__/expr_with_intlimits.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/concrete/__pycache__/expr_with_limits.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/concrete/__pycache__/gosper.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/concrete/__pycache__/guess.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/concrete/__pycache__/products.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/concrete/__pycache__/summations.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/concrete/delta.py +330 -0
- lib/python3.12/site-packages/sympy/concrete/expr_with_intlimits.py +354 -0
- lib/python3.12/site-packages/sympy/concrete/expr_with_limits.py +603 -0
- lib/python3.12/site-packages/sympy/concrete/gosper.py +227 -0
- lib/python3.12/site-packages/sympy/concrete/guess.py +473 -0
- lib/python3.12/site-packages/sympy/concrete/products.py +610 -0
- lib/python3.12/site-packages/sympy/concrete/summations.py +1646 -0
- lib/python3.12/site-packages/sympy/concrete/tests/__init__.py +0 -0
- 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
|
|
|