Add files using upload-large-folder tool
Browse files- lib/python3.12/site-packages/sympy/simplify/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/combsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/cse_opts.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/gammasimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/hyperexpand_doc.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/powsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/radsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/ratsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/simplify.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/sqrtdenest.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/traversaltools.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/__pycache__/trigsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/cse_main.py +946 -0
- lib/python3.12/site-packages/sympy/simplify/epathtools.py +356 -0
- lib/python3.12/site-packages/sympy/simplify/fu.py +2111 -0
- lib/python3.12/site-packages/sympy/simplify/gammasimp.py +497 -0
- lib/python3.12/site-packages/sympy/simplify/ratsimp.py +222 -0
- lib/python3.12/site-packages/sympy/simplify/simplify.py +2150 -0
- lib/python3.12/site-packages/sympy/simplify/sqrtdenest.py +678 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__init__.py +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/__init__.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_combsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_cse.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_epathtools.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_fu.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_function.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_gammasimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_hyperexpand.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_powsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_radsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_ratsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_rewrite.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_simplify.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_sqrtdenest.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_trigsimp.cpython-312.pyc +0 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_combsimp.py +75 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_cse.py +758 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_epathtools.py +90 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_fu.py +492 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_function.py +54 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_gammasimp.py +127 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_hyperexpand.py +1063 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_powsimp.py +366 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_radsimp.py +490 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_ratsimp.py +78 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_rewrite.py +31 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_simplify.py +1087 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_sqrtdenest.py +204 -0
- lib/python3.12/site-packages/sympy/simplify/tests/test_trigsimp.py +520 -0
- lib/python3.12/site-packages/sympy/simplify/trigsimp.py +1253 -0
lib/python3.12/site-packages/sympy/simplify/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (1.41 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/combsimp.cpython-312.pyc
ADDED
|
Binary file (4.91 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/cse_opts.cpython-312.pyc
ADDED
|
Binary file (2.6 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/gammasimp.cpython-312.pyc
ADDED
|
Binary file (19.3 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/hyperexpand_doc.cpython-312.pyc
ADDED
|
Binary file (1.05 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/powsimp.cpython-312.pyc
ADDED
|
Binary file (28.5 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/radsimp.cpython-312.pyc
ADDED
|
Binary file (49.4 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/ratsimp.cpython-312.pyc
ADDED
|
Binary file (11.4 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/simplify.cpython-312.pyc
ADDED
|
Binary file (85.7 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/sqrtdenest.cpython-312.pyc
ADDED
|
Binary file (28.6 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/traversaltools.cpython-312.pyc
ADDED
|
Binary file (659 Bytes). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/__pycache__/trigsimp.cpython-312.pyc
ADDED
|
Binary file (56.3 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/cse_main.py
ADDED
|
@@ -0,0 +1,946 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Tools for doing common subexpression elimination.
|
| 2 |
+
"""
|
| 3 |
+
from collections import defaultdict
|
| 4 |
+
|
| 5 |
+
from sympy.core import Basic, Mul, Add, Pow, sympify
|
| 6 |
+
from sympy.core.containers import Tuple, OrderedSet
|
| 7 |
+
from sympy.core.exprtools import factor_terms
|
| 8 |
+
from sympy.core.singleton import S
|
| 9 |
+
from sympy.core.sorting import ordered
|
| 10 |
+
from sympy.core.symbol import symbols, Symbol
|
| 11 |
+
from sympy.matrices import (MatrixBase, Matrix, ImmutableMatrix,
|
| 12 |
+
SparseMatrix, ImmutableSparseMatrix)
|
| 13 |
+
from sympy.matrices.expressions import (MatrixExpr, MatrixSymbol, MatMul,
|
| 14 |
+
MatAdd, MatPow, Inverse)
|
| 15 |
+
from sympy.matrices.expressions.matexpr import MatrixElement
|
| 16 |
+
from sympy.polys.rootoftools import RootOf
|
| 17 |
+
from sympy.utilities.iterables import numbered_symbols, sift, \
|
| 18 |
+
topological_sort, iterable
|
| 19 |
+
|
| 20 |
+
from . import cse_opts
|
| 21 |
+
|
| 22 |
+
# (preprocessor, postprocessor) pairs which are commonly useful. They should
|
| 23 |
+
# each take a SymPy expression and return a possibly transformed expression.
|
| 24 |
+
# When used in the function ``cse()``, the target expressions will be transformed
|
| 25 |
+
# by each of the preprocessor functions in order. After the common
|
| 26 |
+
# subexpressions are eliminated, each resulting expression will have the
|
| 27 |
+
# postprocessor functions transform them in *reverse* order in order to undo the
|
| 28 |
+
# transformation if necessary. This allows the algorithm to operate on
|
| 29 |
+
# a representation of the expressions that allows for more optimization
|
| 30 |
+
# opportunities.
|
| 31 |
+
# ``None`` can be used to specify no transformation for either the preprocessor or
|
| 32 |
+
# postprocessor.
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
basic_optimizations = [(cse_opts.sub_pre, cse_opts.sub_post),
|
| 36 |
+
(factor_terms, None)]
|
| 37 |
+
|
| 38 |
+
# sometimes we want the output in a different format; non-trivial
|
| 39 |
+
# transformations can be put here for users
|
| 40 |
+
# ===============================================================
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def reps_toposort(r):
|
| 44 |
+
"""Sort replacements ``r`` so (k1, v1) appears before (k2, v2)
|
| 45 |
+
if k2 is in v1's free symbols. This orders items in the
|
| 46 |
+
way that cse returns its results (hence, in order to use the
|
| 47 |
+
replacements in a substitution option it would make sense
|
| 48 |
+
to reverse the order).
|
| 49 |
+
|
| 50 |
+
Examples
|
| 51 |
+
========
|
| 52 |
+
|
| 53 |
+
>>> from sympy.simplify.cse_main import reps_toposort
|
| 54 |
+
>>> from sympy.abc import x, y
|
| 55 |
+
>>> from sympy import Eq
|
| 56 |
+
>>> for l, r in reps_toposort([(x, y + 1), (y, 2)]):
|
| 57 |
+
... print(Eq(l, r))
|
| 58 |
+
...
|
| 59 |
+
Eq(y, 2)
|
| 60 |
+
Eq(x, y + 1)
|
| 61 |
+
|
| 62 |
+
"""
|
| 63 |
+
r = sympify(r)
|
| 64 |
+
E = []
|
| 65 |
+
for c1, (k1, v1) in enumerate(r):
|
| 66 |
+
for c2, (k2, v2) in enumerate(r):
|
| 67 |
+
if k1 in v2.free_symbols:
|
| 68 |
+
E.append((c1, c2))
|
| 69 |
+
return [r[i] for i in topological_sort((range(len(r)), E))]
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def cse_separate(r, e):
|
| 73 |
+
"""Move expressions that are in the form (symbol, expr) out of the
|
| 74 |
+
expressions and sort them into the replacements using the reps_toposort.
|
| 75 |
+
|
| 76 |
+
Examples
|
| 77 |
+
========
|
| 78 |
+
|
| 79 |
+
>>> from sympy.simplify.cse_main import cse_separate
|
| 80 |
+
>>> from sympy.abc import x, y, z
|
| 81 |
+
>>> from sympy import cos, exp, cse, Eq, symbols
|
| 82 |
+
>>> x0, x1 = symbols('x:2')
|
| 83 |
+
>>> eq = (x + 1 + exp((x + 1)/(y + 1)) + cos(y + 1))
|
| 84 |
+
>>> cse([eq, Eq(x, z + 1), z - 2], postprocess=cse_separate) in [
|
| 85 |
+
... [[(x0, y + 1), (x, z + 1), (x1, x + 1)],
|
| 86 |
+
... [x1 + exp(x1/x0) + cos(x0), z - 2]],
|
| 87 |
+
... [[(x1, y + 1), (x, z + 1), (x0, x + 1)],
|
| 88 |
+
... [x0 + exp(x0/x1) + cos(x1), z - 2]]]
|
| 89 |
+
...
|
| 90 |
+
True
|
| 91 |
+
"""
|
| 92 |
+
d = sift(e, lambda w: w.is_Equality and w.lhs.is_Symbol)
|
| 93 |
+
r = r + [w.args for w in d[True]]
|
| 94 |
+
e = d[False]
|
| 95 |
+
return [reps_toposort(r), e]
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def cse_release_variables(r, e):
|
| 99 |
+
"""
|
| 100 |
+
Return tuples giving ``(a, b)`` where ``a`` is a symbol and ``b`` is
|
| 101 |
+
either an expression or None. The value of None is used when a
|
| 102 |
+
symbol is no longer needed for subsequent expressions.
|
| 103 |
+
|
| 104 |
+
Use of such output can reduce the memory footprint of lambdified
|
| 105 |
+
expressions that contain large, repeated subexpressions.
|
| 106 |
+
|
| 107 |
+
Examples
|
| 108 |
+
========
|
| 109 |
+
|
| 110 |
+
>>> from sympy import cse
|
| 111 |
+
>>> from sympy.simplify.cse_main import cse_release_variables
|
| 112 |
+
>>> from sympy.abc import x, y
|
| 113 |
+
>>> eqs = [(x + y - 1)**2, x, x + y, (x + y)/(2*x + 1) + (x + y - 1)**2, (2*x + 1)**(x + y)]
|
| 114 |
+
>>> defs, rvs = cse_release_variables(*cse(eqs))
|
| 115 |
+
>>> for i in defs:
|
| 116 |
+
... print(i)
|
| 117 |
+
...
|
| 118 |
+
(x0, x + y)
|
| 119 |
+
(x1, (x0 - 1)**2)
|
| 120 |
+
(x2, 2*x + 1)
|
| 121 |
+
(_3, x0/x2 + x1)
|
| 122 |
+
(_4, x2**x0)
|
| 123 |
+
(x2, None)
|
| 124 |
+
(_0, x1)
|
| 125 |
+
(x1, None)
|
| 126 |
+
(_2, x0)
|
| 127 |
+
(x0, None)
|
| 128 |
+
(_1, x)
|
| 129 |
+
>>> print(rvs)
|
| 130 |
+
(_0, _1, _2, _3, _4)
|
| 131 |
+
"""
|
| 132 |
+
if not r:
|
| 133 |
+
return r, e
|
| 134 |
+
|
| 135 |
+
s, p = zip(*r)
|
| 136 |
+
esyms = symbols('_:%d' % len(e))
|
| 137 |
+
syms = list(esyms)
|
| 138 |
+
s = list(s)
|
| 139 |
+
in_use = set(s)
|
| 140 |
+
p = list(p)
|
| 141 |
+
# sort e so those with most sub-expressions appear first
|
| 142 |
+
e = [(e[i], syms[i]) for i in range(len(e))]
|
| 143 |
+
e, syms = zip(*sorted(e,
|
| 144 |
+
key=lambda x: -sum(p[s.index(i)].count_ops()
|
| 145 |
+
for i in x[0].free_symbols & in_use)))
|
| 146 |
+
syms = list(syms)
|
| 147 |
+
p += e
|
| 148 |
+
rv = []
|
| 149 |
+
i = len(p) - 1
|
| 150 |
+
while i >= 0:
|
| 151 |
+
_p = p.pop()
|
| 152 |
+
c = in_use & _p.free_symbols
|
| 153 |
+
if c: # sorting for canonical results
|
| 154 |
+
rv.extend([(s, None) for s in sorted(c, key=str)])
|
| 155 |
+
if i >= len(r):
|
| 156 |
+
rv.append((syms.pop(), _p))
|
| 157 |
+
else:
|
| 158 |
+
rv.append((s[i], _p))
|
| 159 |
+
in_use -= c
|
| 160 |
+
i -= 1
|
| 161 |
+
rv.reverse()
|
| 162 |
+
return rv, esyms
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
# ====end of cse postprocess idioms===========================
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def preprocess_for_cse(expr, optimizations):
|
| 169 |
+
""" Preprocess an expression to optimize for common subexpression
|
| 170 |
+
elimination.
|
| 171 |
+
|
| 172 |
+
Parameters
|
| 173 |
+
==========
|
| 174 |
+
|
| 175 |
+
expr : SymPy expression
|
| 176 |
+
The target expression to optimize.
|
| 177 |
+
optimizations : list of (callable, callable) pairs
|
| 178 |
+
The (preprocessor, postprocessor) pairs.
|
| 179 |
+
|
| 180 |
+
Returns
|
| 181 |
+
=======
|
| 182 |
+
|
| 183 |
+
expr : SymPy expression
|
| 184 |
+
The transformed expression.
|
| 185 |
+
"""
|
| 186 |
+
for pre, post in optimizations:
|
| 187 |
+
if pre is not None:
|
| 188 |
+
expr = pre(expr)
|
| 189 |
+
return expr
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def postprocess_for_cse(expr, optimizations):
|
| 193 |
+
"""Postprocess an expression after common subexpression elimination to
|
| 194 |
+
return the expression to canonical SymPy form.
|
| 195 |
+
|
| 196 |
+
Parameters
|
| 197 |
+
==========
|
| 198 |
+
|
| 199 |
+
expr : SymPy expression
|
| 200 |
+
The target expression to transform.
|
| 201 |
+
optimizations : list of (callable, callable) pairs, optional
|
| 202 |
+
The (preprocessor, postprocessor) pairs. The postprocessors will be
|
| 203 |
+
applied in reversed order to undo the effects of the preprocessors
|
| 204 |
+
correctly.
|
| 205 |
+
|
| 206 |
+
Returns
|
| 207 |
+
=======
|
| 208 |
+
|
| 209 |
+
expr : SymPy expression
|
| 210 |
+
The transformed expression.
|
| 211 |
+
"""
|
| 212 |
+
for pre, post in reversed(optimizations):
|
| 213 |
+
if post is not None:
|
| 214 |
+
expr = post(expr)
|
| 215 |
+
return expr
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
class FuncArgTracker:
|
| 219 |
+
"""
|
| 220 |
+
A class which manages a mapping from functions to arguments and an inverse
|
| 221 |
+
mapping from arguments to functions.
|
| 222 |
+
"""
|
| 223 |
+
|
| 224 |
+
def __init__(self, funcs):
|
| 225 |
+
# To minimize the number of symbolic comparisons, all function arguments
|
| 226 |
+
# get assigned a value number.
|
| 227 |
+
self.value_numbers = {}
|
| 228 |
+
self.value_number_to_value = []
|
| 229 |
+
|
| 230 |
+
# Both of these maps use integer indices for arguments / functions.
|
| 231 |
+
self.arg_to_funcset = []
|
| 232 |
+
self.func_to_argset = []
|
| 233 |
+
|
| 234 |
+
for func_i, func in enumerate(funcs):
|
| 235 |
+
func_argset = OrderedSet()
|
| 236 |
+
|
| 237 |
+
for func_arg in func.args:
|
| 238 |
+
arg_number = self.get_or_add_value_number(func_arg)
|
| 239 |
+
func_argset.add(arg_number)
|
| 240 |
+
self.arg_to_funcset[arg_number].add(func_i)
|
| 241 |
+
|
| 242 |
+
self.func_to_argset.append(func_argset)
|
| 243 |
+
|
| 244 |
+
def get_args_in_value_order(self, argset):
|
| 245 |
+
"""
|
| 246 |
+
Return the list of arguments in sorted order according to their value
|
| 247 |
+
numbers.
|
| 248 |
+
"""
|
| 249 |
+
return [self.value_number_to_value[argn] for argn in sorted(argset)]
|
| 250 |
+
|
| 251 |
+
def get_or_add_value_number(self, value):
|
| 252 |
+
"""
|
| 253 |
+
Return the value number for the given argument.
|
| 254 |
+
"""
|
| 255 |
+
nvalues = len(self.value_numbers)
|
| 256 |
+
value_number = self.value_numbers.setdefault(value, nvalues)
|
| 257 |
+
if value_number == nvalues:
|
| 258 |
+
self.value_number_to_value.append(value)
|
| 259 |
+
self.arg_to_funcset.append(OrderedSet())
|
| 260 |
+
return value_number
|
| 261 |
+
|
| 262 |
+
def stop_arg_tracking(self, func_i):
|
| 263 |
+
"""
|
| 264 |
+
Remove the function func_i from the argument to function mapping.
|
| 265 |
+
"""
|
| 266 |
+
for arg in self.func_to_argset[func_i]:
|
| 267 |
+
self.arg_to_funcset[arg].remove(func_i)
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def get_common_arg_candidates(self, argset, min_func_i=0):
|
| 271 |
+
"""Return a dict whose keys are function numbers. The entries of the dict are
|
| 272 |
+
the number of arguments said function has in common with
|
| 273 |
+
``argset``. Entries have at least 2 items in common. All keys have
|
| 274 |
+
value at least ``min_func_i``.
|
| 275 |
+
"""
|
| 276 |
+
count_map = defaultdict(lambda: 0)
|
| 277 |
+
if not argset:
|
| 278 |
+
return count_map
|
| 279 |
+
|
| 280 |
+
funcsets = [self.arg_to_funcset[arg] for arg in argset]
|
| 281 |
+
# As an optimization below, we handle the largest funcset separately from
|
| 282 |
+
# the others.
|
| 283 |
+
largest_funcset = max(funcsets, key=len)
|
| 284 |
+
|
| 285 |
+
for funcset in funcsets:
|
| 286 |
+
if largest_funcset is funcset:
|
| 287 |
+
continue
|
| 288 |
+
for func_i in funcset:
|
| 289 |
+
if func_i >= min_func_i:
|
| 290 |
+
count_map[func_i] += 1
|
| 291 |
+
|
| 292 |
+
# We pick the smaller of the two containers (count_map, largest_funcset)
|
| 293 |
+
# to iterate over to reduce the number of iterations needed.
|
| 294 |
+
(smaller_funcs_container,
|
| 295 |
+
larger_funcs_container) = sorted(
|
| 296 |
+
[largest_funcset, count_map],
|
| 297 |
+
key=len)
|
| 298 |
+
|
| 299 |
+
for func_i in smaller_funcs_container:
|
| 300 |
+
# Not already in count_map? It can't possibly be in the output, so
|
| 301 |
+
# skip it.
|
| 302 |
+
if count_map[func_i] < 1:
|
| 303 |
+
continue
|
| 304 |
+
|
| 305 |
+
if func_i in larger_funcs_container:
|
| 306 |
+
count_map[func_i] += 1
|
| 307 |
+
|
| 308 |
+
return {k: v for k, v in count_map.items() if v >= 2}
|
| 309 |
+
|
| 310 |
+
def get_subset_candidates(self, argset, restrict_to_funcset=None):
|
| 311 |
+
"""
|
| 312 |
+
Return a set of functions each of which whose argument list contains
|
| 313 |
+
``argset``, optionally filtered only to contain functions in
|
| 314 |
+
``restrict_to_funcset``.
|
| 315 |
+
"""
|
| 316 |
+
iarg = iter(argset)
|
| 317 |
+
|
| 318 |
+
indices = OrderedSet(
|
| 319 |
+
fi for fi in self.arg_to_funcset[next(iarg)])
|
| 320 |
+
|
| 321 |
+
if restrict_to_funcset is not None:
|
| 322 |
+
indices &= restrict_to_funcset
|
| 323 |
+
|
| 324 |
+
for arg in iarg:
|
| 325 |
+
indices &= self.arg_to_funcset[arg]
|
| 326 |
+
|
| 327 |
+
return indices
|
| 328 |
+
|
| 329 |
+
def update_func_argset(self, func_i, new_argset):
|
| 330 |
+
"""
|
| 331 |
+
Update a function with a new set of arguments.
|
| 332 |
+
"""
|
| 333 |
+
new_args = OrderedSet(new_argset)
|
| 334 |
+
old_args = self.func_to_argset[func_i]
|
| 335 |
+
|
| 336 |
+
for deleted_arg in old_args - new_args:
|
| 337 |
+
self.arg_to_funcset[deleted_arg].remove(func_i)
|
| 338 |
+
for added_arg in new_args - old_args:
|
| 339 |
+
self.arg_to_funcset[added_arg].add(func_i)
|
| 340 |
+
|
| 341 |
+
self.func_to_argset[func_i].clear()
|
| 342 |
+
self.func_to_argset[func_i].update(new_args)
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
class Unevaluated:
|
| 346 |
+
|
| 347 |
+
def __init__(self, func, args):
|
| 348 |
+
self.func = func
|
| 349 |
+
self.args = args
|
| 350 |
+
|
| 351 |
+
def __str__(self):
|
| 352 |
+
return "Uneval<{}>({})".format(
|
| 353 |
+
self.func, ", ".join(str(a) for a in self.args))
|
| 354 |
+
|
| 355 |
+
def as_unevaluated_basic(self):
|
| 356 |
+
return self.func(*self.args, evaluate=False)
|
| 357 |
+
|
| 358 |
+
@property
|
| 359 |
+
def free_symbols(self):
|
| 360 |
+
return set().union(*[a.free_symbols for a in self.args])
|
| 361 |
+
|
| 362 |
+
__repr__ = __str__
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
def match_common_args(func_class, funcs, opt_subs):
|
| 366 |
+
"""
|
| 367 |
+
Recognize and extract common subexpressions of function arguments within a
|
| 368 |
+
set of function calls. For instance, for the following function calls::
|
| 369 |
+
|
| 370 |
+
x + z + y
|
| 371 |
+
sin(x + y)
|
| 372 |
+
|
| 373 |
+
this will extract a common subexpression of `x + y`::
|
| 374 |
+
|
| 375 |
+
w = x + y
|
| 376 |
+
w + z
|
| 377 |
+
sin(w)
|
| 378 |
+
|
| 379 |
+
The function we work with is assumed to be associative and commutative.
|
| 380 |
+
|
| 381 |
+
Parameters
|
| 382 |
+
==========
|
| 383 |
+
|
| 384 |
+
func_class: class
|
| 385 |
+
The function class (e.g. Add, Mul)
|
| 386 |
+
funcs: list of functions
|
| 387 |
+
A list of function calls.
|
| 388 |
+
opt_subs: dict
|
| 389 |
+
A dictionary of substitutions which this function may update.
|
| 390 |
+
"""
|
| 391 |
+
|
| 392 |
+
# Sort to ensure that whole-function subexpressions come before the items
|
| 393 |
+
# that use them.
|
| 394 |
+
funcs = sorted(funcs, key=lambda f: len(f.args))
|
| 395 |
+
arg_tracker = FuncArgTracker(funcs)
|
| 396 |
+
|
| 397 |
+
changed = OrderedSet()
|
| 398 |
+
|
| 399 |
+
for i in range(len(funcs)):
|
| 400 |
+
common_arg_candidates_counts = arg_tracker.get_common_arg_candidates(
|
| 401 |
+
arg_tracker.func_to_argset[i], min_func_i=i + 1)
|
| 402 |
+
|
| 403 |
+
# Sort the candidates in order of match size.
|
| 404 |
+
# This makes us try combining smaller matches first.
|
| 405 |
+
common_arg_candidates = OrderedSet(sorted(
|
| 406 |
+
common_arg_candidates_counts.keys(),
|
| 407 |
+
key=lambda k: (common_arg_candidates_counts[k], k)))
|
| 408 |
+
|
| 409 |
+
while common_arg_candidates:
|
| 410 |
+
j = common_arg_candidates.pop(last=False)
|
| 411 |
+
|
| 412 |
+
com_args = arg_tracker.func_to_argset[i].intersection(
|
| 413 |
+
arg_tracker.func_to_argset[j])
|
| 414 |
+
|
| 415 |
+
if len(com_args) <= 1:
|
| 416 |
+
# This may happen if a set of common arguments was already
|
| 417 |
+
# combined in a previous iteration.
|
| 418 |
+
continue
|
| 419 |
+
|
| 420 |
+
# For all sets, replace the common symbols by the function
|
| 421 |
+
# over them, to allow recursive matches.
|
| 422 |
+
|
| 423 |
+
diff_i = arg_tracker.func_to_argset[i].difference(com_args)
|
| 424 |
+
if diff_i:
|
| 425 |
+
# com_func needs to be unevaluated to allow for recursive matches.
|
| 426 |
+
com_func = Unevaluated(
|
| 427 |
+
func_class, arg_tracker.get_args_in_value_order(com_args))
|
| 428 |
+
com_func_number = arg_tracker.get_or_add_value_number(com_func)
|
| 429 |
+
arg_tracker.update_func_argset(i, diff_i | OrderedSet([com_func_number]))
|
| 430 |
+
changed.add(i)
|
| 431 |
+
else:
|
| 432 |
+
# Treat the whole expression as a CSE.
|
| 433 |
+
#
|
| 434 |
+
# The reason this needs to be done is somewhat subtle. Within
|
| 435 |
+
# tree_cse(), to_eliminate only contains expressions that are
|
| 436 |
+
# seen more than once. The problem is unevaluated expressions
|
| 437 |
+
# do not compare equal to the evaluated equivalent. So
|
| 438 |
+
# tree_cse() won't mark funcs[i] as a CSE if we use an
|
| 439 |
+
# unevaluated version.
|
| 440 |
+
com_func_number = arg_tracker.get_or_add_value_number(funcs[i])
|
| 441 |
+
|
| 442 |
+
diff_j = arg_tracker.func_to_argset[j].difference(com_args)
|
| 443 |
+
arg_tracker.update_func_argset(j, diff_j | OrderedSet([com_func_number]))
|
| 444 |
+
changed.add(j)
|
| 445 |
+
|
| 446 |
+
for k in arg_tracker.get_subset_candidates(
|
| 447 |
+
com_args, common_arg_candidates):
|
| 448 |
+
diff_k = arg_tracker.func_to_argset[k].difference(com_args)
|
| 449 |
+
arg_tracker.update_func_argset(k, diff_k | OrderedSet([com_func_number]))
|
| 450 |
+
changed.add(k)
|
| 451 |
+
|
| 452 |
+
if i in changed:
|
| 453 |
+
opt_subs[funcs[i]] = Unevaluated(func_class,
|
| 454 |
+
arg_tracker.get_args_in_value_order(arg_tracker.func_to_argset[i]))
|
| 455 |
+
|
| 456 |
+
arg_tracker.stop_arg_tracking(i)
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
def opt_cse(exprs, order='canonical'):
|
| 460 |
+
"""Find optimization opportunities in Adds, Muls, Pows and negative
|
| 461 |
+
coefficient Muls.
|
| 462 |
+
|
| 463 |
+
Parameters
|
| 464 |
+
==========
|
| 465 |
+
|
| 466 |
+
exprs : list of SymPy expressions
|
| 467 |
+
The expressions to optimize.
|
| 468 |
+
order : string, 'none' or 'canonical'
|
| 469 |
+
The order by which Mul and Add arguments are processed. For large
|
| 470 |
+
expressions where speed is a concern, use the setting order='none'.
|
| 471 |
+
|
| 472 |
+
Returns
|
| 473 |
+
=======
|
| 474 |
+
|
| 475 |
+
opt_subs : dictionary of expression substitutions
|
| 476 |
+
The expression substitutions which can be useful to optimize CSE.
|
| 477 |
+
|
| 478 |
+
Examples
|
| 479 |
+
========
|
| 480 |
+
|
| 481 |
+
>>> from sympy.simplify.cse_main import opt_cse
|
| 482 |
+
>>> from sympy.abc import x
|
| 483 |
+
>>> opt_subs = opt_cse([x**-2])
|
| 484 |
+
>>> k, v = list(opt_subs.keys())[0], list(opt_subs.values())[0]
|
| 485 |
+
>>> print((k, v.as_unevaluated_basic()))
|
| 486 |
+
(x**(-2), 1/(x**2))
|
| 487 |
+
"""
|
| 488 |
+
opt_subs = {}
|
| 489 |
+
|
| 490 |
+
adds = OrderedSet()
|
| 491 |
+
muls = OrderedSet()
|
| 492 |
+
|
| 493 |
+
seen_subexp = set()
|
| 494 |
+
collapsible_subexp = set()
|
| 495 |
+
|
| 496 |
+
def _find_opts(expr):
|
| 497 |
+
|
| 498 |
+
if not isinstance(expr, (Basic, Unevaluated)):
|
| 499 |
+
return
|
| 500 |
+
|
| 501 |
+
if expr.is_Atom or expr.is_Order:
|
| 502 |
+
return
|
| 503 |
+
|
| 504 |
+
if iterable(expr):
|
| 505 |
+
list(map(_find_opts, expr))
|
| 506 |
+
return
|
| 507 |
+
|
| 508 |
+
if expr in seen_subexp:
|
| 509 |
+
return expr
|
| 510 |
+
seen_subexp.add(expr)
|
| 511 |
+
|
| 512 |
+
list(map(_find_opts, expr.args))
|
| 513 |
+
|
| 514 |
+
if not isinstance(expr, MatrixExpr) and expr.could_extract_minus_sign():
|
| 515 |
+
# XXX -expr does not always work rigorously for some expressions
|
| 516 |
+
# containing UnevaluatedExpr.
|
| 517 |
+
# https://github.com/sympy/sympy/issues/24818
|
| 518 |
+
if isinstance(expr, Add):
|
| 519 |
+
neg_expr = Add(*(-i for i in expr.args))
|
| 520 |
+
else:
|
| 521 |
+
neg_expr = -expr
|
| 522 |
+
|
| 523 |
+
if not neg_expr.is_Atom:
|
| 524 |
+
opt_subs[expr] = Unevaluated(Mul, (S.NegativeOne, neg_expr))
|
| 525 |
+
seen_subexp.add(neg_expr)
|
| 526 |
+
expr = neg_expr
|
| 527 |
+
|
| 528 |
+
if isinstance(expr, (Mul, MatMul)):
|
| 529 |
+
if len(expr.args) == 1:
|
| 530 |
+
collapsible_subexp.add(expr)
|
| 531 |
+
else:
|
| 532 |
+
muls.add(expr)
|
| 533 |
+
|
| 534 |
+
elif isinstance(expr, (Add, MatAdd)):
|
| 535 |
+
if len(expr.args) == 1:
|
| 536 |
+
collapsible_subexp.add(expr)
|
| 537 |
+
else:
|
| 538 |
+
adds.add(expr)
|
| 539 |
+
|
| 540 |
+
elif isinstance(expr, Inverse):
|
| 541 |
+
# Do not want to treat `Inverse` as a `MatPow`
|
| 542 |
+
pass
|
| 543 |
+
|
| 544 |
+
elif isinstance(expr, (Pow, MatPow)):
|
| 545 |
+
base, exp = expr.base, expr.exp
|
| 546 |
+
if exp.could_extract_minus_sign():
|
| 547 |
+
opt_subs[expr] = Unevaluated(Pow, (Pow(base, -exp), -1))
|
| 548 |
+
|
| 549 |
+
for e in exprs:
|
| 550 |
+
if isinstance(e, (Basic, Unevaluated)):
|
| 551 |
+
_find_opts(e)
|
| 552 |
+
|
| 553 |
+
# Handle collapsing of multinary operations with single arguments
|
| 554 |
+
edges = [(s, s.args[0]) for s in collapsible_subexp
|
| 555 |
+
if s.args[0] in collapsible_subexp]
|
| 556 |
+
for e in reversed(topological_sort((collapsible_subexp, edges))):
|
| 557 |
+
opt_subs[e] = opt_subs.get(e.args[0], e.args[0])
|
| 558 |
+
|
| 559 |
+
# split muls into commutative
|
| 560 |
+
commutative_muls = OrderedSet()
|
| 561 |
+
for m in muls:
|
| 562 |
+
c, nc = m.args_cnc(cset=False)
|
| 563 |
+
if c:
|
| 564 |
+
c_mul = m.func(*c)
|
| 565 |
+
if nc:
|
| 566 |
+
if c_mul == 1:
|
| 567 |
+
new_obj = m.func(*nc)
|
| 568 |
+
else:
|
| 569 |
+
if isinstance(m, MatMul):
|
| 570 |
+
new_obj = m.func(c_mul, *nc, evaluate=False)
|
| 571 |
+
else:
|
| 572 |
+
new_obj = m.func(c_mul, m.func(*nc), evaluate=False)
|
| 573 |
+
opt_subs[m] = new_obj
|
| 574 |
+
if len(c) > 1:
|
| 575 |
+
commutative_muls.add(c_mul)
|
| 576 |
+
|
| 577 |
+
match_common_args(Add, adds, opt_subs)
|
| 578 |
+
match_common_args(Mul, commutative_muls, opt_subs)
|
| 579 |
+
|
| 580 |
+
return opt_subs
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
def tree_cse(exprs, symbols, opt_subs=None, order='canonical', ignore=()):
|
| 584 |
+
"""Perform raw CSE on expression tree, taking opt_subs into account.
|
| 585 |
+
|
| 586 |
+
Parameters
|
| 587 |
+
==========
|
| 588 |
+
|
| 589 |
+
exprs : list of SymPy expressions
|
| 590 |
+
The expressions to reduce.
|
| 591 |
+
symbols : infinite iterator yielding unique Symbols
|
| 592 |
+
The symbols used to label the common subexpressions which are pulled
|
| 593 |
+
out.
|
| 594 |
+
opt_subs : dictionary of expression substitutions
|
| 595 |
+
The expressions to be substituted before any CSE action is performed.
|
| 596 |
+
order : string, 'none' or 'canonical'
|
| 597 |
+
The order by which Mul and Add arguments are processed. For large
|
| 598 |
+
expressions where speed is a concern, use the setting order='none'.
|
| 599 |
+
ignore : iterable of Symbols
|
| 600 |
+
Substitutions containing any Symbol from ``ignore`` will be ignored.
|
| 601 |
+
"""
|
| 602 |
+
if opt_subs is None:
|
| 603 |
+
opt_subs = {}
|
| 604 |
+
|
| 605 |
+
## Find repeated sub-expressions
|
| 606 |
+
|
| 607 |
+
to_eliminate = set()
|
| 608 |
+
|
| 609 |
+
seen_subexp = set()
|
| 610 |
+
excluded_symbols = set()
|
| 611 |
+
|
| 612 |
+
def _find_repeated(expr):
|
| 613 |
+
if not isinstance(expr, (Basic, Unevaluated)):
|
| 614 |
+
return
|
| 615 |
+
|
| 616 |
+
if isinstance(expr, RootOf):
|
| 617 |
+
return
|
| 618 |
+
|
| 619 |
+
if isinstance(expr, Basic) and (
|
| 620 |
+
expr.is_Atom or
|
| 621 |
+
expr.is_Order or
|
| 622 |
+
isinstance(expr, (MatrixSymbol, MatrixElement))):
|
| 623 |
+
if expr.is_Symbol:
|
| 624 |
+
excluded_symbols.add(expr.name)
|
| 625 |
+
return
|
| 626 |
+
|
| 627 |
+
if iterable(expr):
|
| 628 |
+
args = expr
|
| 629 |
+
|
| 630 |
+
else:
|
| 631 |
+
if expr in seen_subexp:
|
| 632 |
+
for ign in ignore:
|
| 633 |
+
if ign in expr.free_symbols:
|
| 634 |
+
break
|
| 635 |
+
else:
|
| 636 |
+
to_eliminate.add(expr)
|
| 637 |
+
return
|
| 638 |
+
|
| 639 |
+
seen_subexp.add(expr)
|
| 640 |
+
|
| 641 |
+
if expr in opt_subs:
|
| 642 |
+
expr = opt_subs[expr]
|
| 643 |
+
|
| 644 |
+
args = expr.args
|
| 645 |
+
|
| 646 |
+
list(map(_find_repeated, args))
|
| 647 |
+
|
| 648 |
+
for e in exprs:
|
| 649 |
+
if isinstance(e, Basic):
|
| 650 |
+
_find_repeated(e)
|
| 651 |
+
|
| 652 |
+
## Rebuild tree
|
| 653 |
+
|
| 654 |
+
# Remove symbols from the generator that conflict with names in the expressions.
|
| 655 |
+
symbols = (_ for _ in symbols if _.name not in excluded_symbols)
|
| 656 |
+
|
| 657 |
+
replacements = []
|
| 658 |
+
|
| 659 |
+
subs = {}
|
| 660 |
+
|
| 661 |
+
def _rebuild(expr):
|
| 662 |
+
if not isinstance(expr, (Basic, Unevaluated)):
|
| 663 |
+
return expr
|
| 664 |
+
|
| 665 |
+
if not expr.args:
|
| 666 |
+
return expr
|
| 667 |
+
|
| 668 |
+
if iterable(expr):
|
| 669 |
+
new_args = [_rebuild(arg) for arg in expr.args]
|
| 670 |
+
return expr.func(*new_args)
|
| 671 |
+
|
| 672 |
+
if expr in subs:
|
| 673 |
+
return subs[expr]
|
| 674 |
+
|
| 675 |
+
orig_expr = expr
|
| 676 |
+
if expr in opt_subs:
|
| 677 |
+
expr = opt_subs[expr]
|
| 678 |
+
|
| 679 |
+
# If enabled, parse Muls and Adds arguments by order to ensure
|
| 680 |
+
# replacement order independent from hashes
|
| 681 |
+
if order != 'none':
|
| 682 |
+
if isinstance(expr, (Mul, MatMul)):
|
| 683 |
+
c, nc = expr.args_cnc()
|
| 684 |
+
if c == [1]:
|
| 685 |
+
args = nc
|
| 686 |
+
else:
|
| 687 |
+
args = list(ordered(c)) + nc
|
| 688 |
+
elif isinstance(expr, (Add, MatAdd)):
|
| 689 |
+
args = list(ordered(expr.args))
|
| 690 |
+
else:
|
| 691 |
+
args = expr.args
|
| 692 |
+
else:
|
| 693 |
+
args = expr.args
|
| 694 |
+
|
| 695 |
+
new_args = list(map(_rebuild, args))
|
| 696 |
+
if isinstance(expr, Unevaluated) or new_args != args:
|
| 697 |
+
new_expr = expr.func(*new_args)
|
| 698 |
+
else:
|
| 699 |
+
new_expr = expr
|
| 700 |
+
|
| 701 |
+
if orig_expr in to_eliminate:
|
| 702 |
+
try:
|
| 703 |
+
sym = next(symbols)
|
| 704 |
+
except StopIteration:
|
| 705 |
+
raise ValueError("Symbols iterator ran out of symbols.")
|
| 706 |
+
|
| 707 |
+
if isinstance(orig_expr, MatrixExpr):
|
| 708 |
+
sym = MatrixSymbol(sym.name, orig_expr.rows,
|
| 709 |
+
orig_expr.cols)
|
| 710 |
+
|
| 711 |
+
subs[orig_expr] = sym
|
| 712 |
+
replacements.append((sym, new_expr))
|
| 713 |
+
return sym
|
| 714 |
+
|
| 715 |
+
else:
|
| 716 |
+
return new_expr
|
| 717 |
+
|
| 718 |
+
reduced_exprs = []
|
| 719 |
+
for e in exprs:
|
| 720 |
+
if isinstance(e, Basic):
|
| 721 |
+
reduced_e = _rebuild(e)
|
| 722 |
+
else:
|
| 723 |
+
reduced_e = e
|
| 724 |
+
reduced_exprs.append(reduced_e)
|
| 725 |
+
return replacements, reduced_exprs
|
| 726 |
+
|
| 727 |
+
|
| 728 |
+
def cse(exprs, symbols=None, optimizations=None, postprocess=None,
|
| 729 |
+
order='canonical', ignore=(), list=True):
|
| 730 |
+
""" Perform common subexpression elimination on an expression.
|
| 731 |
+
|
| 732 |
+
Parameters
|
| 733 |
+
==========
|
| 734 |
+
|
| 735 |
+
exprs : list of SymPy expressions, or a single SymPy expression
|
| 736 |
+
The expressions to reduce.
|
| 737 |
+
symbols : infinite iterator yielding unique Symbols
|
| 738 |
+
The symbols used to label the common subexpressions which are pulled
|
| 739 |
+
out. The ``numbered_symbols`` generator is useful. The default is a
|
| 740 |
+
stream of symbols of the form "x0", "x1", etc. This must be an
|
| 741 |
+
infinite iterator.
|
| 742 |
+
optimizations : list of (callable, callable) pairs
|
| 743 |
+
The (preprocessor, postprocessor) pairs of external optimization
|
| 744 |
+
functions. Optionally 'basic' can be passed for a set of predefined
|
| 745 |
+
basic optimizations. Such 'basic' optimizations were used by default
|
| 746 |
+
in old implementation, however they can be really slow on larger
|
| 747 |
+
expressions. Now, no pre or post optimizations are made by default.
|
| 748 |
+
postprocess : a function which accepts the two return values of cse and
|
| 749 |
+
returns the desired form of output from cse, e.g. if you want the
|
| 750 |
+
replacements reversed the function might be the following lambda:
|
| 751 |
+
lambda r, e: return reversed(r), e
|
| 752 |
+
order : string, 'none' or 'canonical'
|
| 753 |
+
The order by which Mul and Add arguments are processed. If set to
|
| 754 |
+
'canonical', arguments will be canonically ordered. If set to 'none',
|
| 755 |
+
ordering will be faster but dependent on expressions hashes, thus
|
| 756 |
+
machine dependent and variable. For large expressions where speed is a
|
| 757 |
+
concern, use the setting order='none'.
|
| 758 |
+
ignore : iterable of Symbols
|
| 759 |
+
Substitutions containing any Symbol from ``ignore`` will be ignored.
|
| 760 |
+
list : bool, (default True)
|
| 761 |
+
Returns expression in list or else with same type as input (when False).
|
| 762 |
+
|
| 763 |
+
Returns
|
| 764 |
+
=======
|
| 765 |
+
|
| 766 |
+
replacements : list of (Symbol, expression) pairs
|
| 767 |
+
All of the common subexpressions that were replaced. Subexpressions
|
| 768 |
+
earlier in this list might show up in subexpressions later in this
|
| 769 |
+
list.
|
| 770 |
+
reduced_exprs : list of SymPy expressions
|
| 771 |
+
The reduced expressions with all of the replacements above.
|
| 772 |
+
|
| 773 |
+
Examples
|
| 774 |
+
========
|
| 775 |
+
|
| 776 |
+
>>> from sympy import cse, SparseMatrix
|
| 777 |
+
>>> from sympy.abc import x, y, z, w
|
| 778 |
+
>>> cse(((w + x + y + z)*(w + y + z))/(w + x)**3)
|
| 779 |
+
([(x0, y + z), (x1, w + x)], [(w + x0)*(x0 + x1)/x1**3])
|
| 780 |
+
|
| 781 |
+
|
| 782 |
+
List of expressions with recursive substitutions:
|
| 783 |
+
|
| 784 |
+
>>> m = SparseMatrix([x + y, x + y + z])
|
| 785 |
+
>>> cse([(x+y)**2, x + y + z, y + z, x + z + y, m])
|
| 786 |
+
([(x0, x + y), (x1, x0 + z)], [x0**2, x1, y + z, x1, Matrix([
|
| 787 |
+
[x0],
|
| 788 |
+
[x1]])])
|
| 789 |
+
|
| 790 |
+
Note: the type and mutability of input matrices is retained.
|
| 791 |
+
|
| 792 |
+
>>> isinstance(_[1][-1], SparseMatrix)
|
| 793 |
+
True
|
| 794 |
+
|
| 795 |
+
The user may disallow substitutions containing certain symbols:
|
| 796 |
+
|
| 797 |
+
>>> cse([y**2*(x + 1), 3*y**2*(x + 1)], ignore=(y,))
|
| 798 |
+
([(x0, x + 1)], [x0*y**2, 3*x0*y**2])
|
| 799 |
+
|
| 800 |
+
The default return value for the reduced expression(s) is a list, even if there is only
|
| 801 |
+
one expression. The `list` flag preserves the type of the input in the output:
|
| 802 |
+
|
| 803 |
+
>>> cse(x)
|
| 804 |
+
([], [x])
|
| 805 |
+
>>> cse(x, list=False)
|
| 806 |
+
([], x)
|
| 807 |
+
"""
|
| 808 |
+
if not list:
|
| 809 |
+
return _cse_homogeneous(exprs,
|
| 810 |
+
symbols=symbols, optimizations=optimizations,
|
| 811 |
+
postprocess=postprocess, order=order, ignore=ignore)
|
| 812 |
+
|
| 813 |
+
if isinstance(exprs, (int, float)):
|
| 814 |
+
exprs = sympify(exprs)
|
| 815 |
+
|
| 816 |
+
# Handle the case if just one expression was passed.
|
| 817 |
+
if isinstance(exprs, (Basic, MatrixBase)):
|
| 818 |
+
exprs = [exprs]
|
| 819 |
+
|
| 820 |
+
copy = exprs
|
| 821 |
+
temp = []
|
| 822 |
+
for e in exprs:
|
| 823 |
+
if isinstance(e, (Matrix, ImmutableMatrix)):
|
| 824 |
+
temp.append(Tuple(*e.flat()))
|
| 825 |
+
elif isinstance(e, (SparseMatrix, ImmutableSparseMatrix)):
|
| 826 |
+
temp.append(Tuple(*e.todok().items()))
|
| 827 |
+
else:
|
| 828 |
+
temp.append(e)
|
| 829 |
+
exprs = temp
|
| 830 |
+
del temp
|
| 831 |
+
|
| 832 |
+
if optimizations is None:
|
| 833 |
+
optimizations = []
|
| 834 |
+
elif optimizations == 'basic':
|
| 835 |
+
optimizations = basic_optimizations
|
| 836 |
+
|
| 837 |
+
# Preprocess the expressions to give us better optimization opportunities.
|
| 838 |
+
reduced_exprs = [preprocess_for_cse(e, optimizations) for e in exprs]
|
| 839 |
+
|
| 840 |
+
if symbols is None:
|
| 841 |
+
symbols = numbered_symbols(cls=Symbol)
|
| 842 |
+
else:
|
| 843 |
+
# In case we get passed an iterable with an __iter__ method instead of
|
| 844 |
+
# an actual iterator.
|
| 845 |
+
symbols = iter(symbols)
|
| 846 |
+
|
| 847 |
+
# Find other optimization opportunities.
|
| 848 |
+
opt_subs = opt_cse(reduced_exprs, order)
|
| 849 |
+
|
| 850 |
+
# Main CSE algorithm.
|
| 851 |
+
replacements, reduced_exprs = tree_cse(reduced_exprs, symbols, opt_subs,
|
| 852 |
+
order, ignore)
|
| 853 |
+
|
| 854 |
+
# Postprocess the expressions to return the expressions to canonical form.
|
| 855 |
+
exprs = copy
|
| 856 |
+
for i, (sym, subtree) in enumerate(replacements):
|
| 857 |
+
subtree = postprocess_for_cse(subtree, optimizations)
|
| 858 |
+
replacements[i] = (sym, subtree)
|
| 859 |
+
reduced_exprs = [postprocess_for_cse(e, optimizations)
|
| 860 |
+
for e in reduced_exprs]
|
| 861 |
+
|
| 862 |
+
# Get the matrices back
|
| 863 |
+
for i, e in enumerate(exprs):
|
| 864 |
+
if isinstance(e, (Matrix, ImmutableMatrix)):
|
| 865 |
+
reduced_exprs[i] = Matrix(e.rows, e.cols, reduced_exprs[i])
|
| 866 |
+
if isinstance(e, ImmutableMatrix):
|
| 867 |
+
reduced_exprs[i] = reduced_exprs[i].as_immutable()
|
| 868 |
+
elif isinstance(e, (SparseMatrix, ImmutableSparseMatrix)):
|
| 869 |
+
m = SparseMatrix(e.rows, e.cols, {})
|
| 870 |
+
for k, v in reduced_exprs[i]:
|
| 871 |
+
m[k] = v
|
| 872 |
+
if isinstance(e, ImmutableSparseMatrix):
|
| 873 |
+
m = m.as_immutable()
|
| 874 |
+
reduced_exprs[i] = m
|
| 875 |
+
|
| 876 |
+
if postprocess is None:
|
| 877 |
+
return replacements, reduced_exprs
|
| 878 |
+
|
| 879 |
+
return postprocess(replacements, reduced_exprs)
|
| 880 |
+
|
| 881 |
+
|
| 882 |
+
def _cse_homogeneous(exprs, **kwargs):
|
| 883 |
+
"""
|
| 884 |
+
Same as ``cse`` but the ``reduced_exprs`` are returned
|
| 885 |
+
with the same type as ``exprs`` or a sympified version of the same.
|
| 886 |
+
|
| 887 |
+
Parameters
|
| 888 |
+
==========
|
| 889 |
+
|
| 890 |
+
exprs : an Expr, iterable of Expr or dictionary with Expr values
|
| 891 |
+
the expressions in which repeated subexpressions will be identified
|
| 892 |
+
kwargs : additional arguments for the ``cse`` function
|
| 893 |
+
|
| 894 |
+
Returns
|
| 895 |
+
=======
|
| 896 |
+
|
| 897 |
+
replacements : list of (Symbol, expression) pairs
|
| 898 |
+
All of the common subexpressions that were replaced. Subexpressions
|
| 899 |
+
earlier in this list might show up in subexpressions later in this
|
| 900 |
+
list.
|
| 901 |
+
reduced_exprs : list of SymPy expressions
|
| 902 |
+
The reduced expressions with all of the replacements above.
|
| 903 |
+
|
| 904 |
+
Examples
|
| 905 |
+
========
|
| 906 |
+
|
| 907 |
+
>>> from sympy.simplify.cse_main import cse
|
| 908 |
+
>>> from sympy import cos, Tuple, Matrix
|
| 909 |
+
>>> from sympy.abc import x
|
| 910 |
+
>>> output = lambda x: type(cse(x, list=False)[1])
|
| 911 |
+
>>> output(1)
|
| 912 |
+
<class 'sympy.core.numbers.One'>
|
| 913 |
+
>>> output('cos(x)')
|
| 914 |
+
<class 'str'>
|
| 915 |
+
>>> output(cos(x))
|
| 916 |
+
cos
|
| 917 |
+
>>> output(Tuple(1, x))
|
| 918 |
+
<class 'sympy.core.containers.Tuple'>
|
| 919 |
+
>>> output(Matrix([[1,0], [0,1]]))
|
| 920 |
+
<class 'sympy.matrices.dense.MutableDenseMatrix'>
|
| 921 |
+
>>> output([1, x])
|
| 922 |
+
<class 'list'>
|
| 923 |
+
>>> output((1, x))
|
| 924 |
+
<class 'tuple'>
|
| 925 |
+
>>> output({1, x})
|
| 926 |
+
<class 'set'>
|
| 927 |
+
"""
|
| 928 |
+
if isinstance(exprs, str):
|
| 929 |
+
replacements, reduced_exprs = _cse_homogeneous(
|
| 930 |
+
sympify(exprs), **kwargs)
|
| 931 |
+
return replacements, repr(reduced_exprs)
|
| 932 |
+
if isinstance(exprs, (list, tuple, set)):
|
| 933 |
+
replacements, reduced_exprs = cse(exprs, **kwargs)
|
| 934 |
+
return replacements, type(exprs)(reduced_exprs)
|
| 935 |
+
if isinstance(exprs, dict):
|
| 936 |
+
keys = list(exprs.keys()) # In order to guarantee the order of the elements.
|
| 937 |
+
replacements, values = cse([exprs[k] for k in keys], **kwargs)
|
| 938 |
+
reduced_exprs = dict(zip(keys, values))
|
| 939 |
+
return replacements, reduced_exprs
|
| 940 |
+
|
| 941 |
+
try:
|
| 942 |
+
replacements, (reduced_exprs,) = cse(exprs, **kwargs)
|
| 943 |
+
except TypeError: # For example 'mpf' objects
|
| 944 |
+
return [], exprs
|
| 945 |
+
else:
|
| 946 |
+
return replacements, reduced_exprs
|
lib/python3.12/site-packages/sympy/simplify/epathtools.py
ADDED
|
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tools for manipulation of expressions using paths. """
|
| 2 |
+
|
| 3 |
+
from sympy.core import Basic
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class EPath:
|
| 7 |
+
r"""
|
| 8 |
+
Manipulate expressions using paths.
|
| 9 |
+
|
| 10 |
+
EPath grammar in EBNF notation::
|
| 11 |
+
|
| 12 |
+
literal ::= /[A-Za-z_][A-Za-z_0-9]*/
|
| 13 |
+
number ::= /-?\d+/
|
| 14 |
+
type ::= literal
|
| 15 |
+
attribute ::= literal "?"
|
| 16 |
+
all ::= "*"
|
| 17 |
+
slice ::= "[" number? (":" number? (":" number?)?)? "]"
|
| 18 |
+
range ::= all | slice
|
| 19 |
+
query ::= (type | attribute) ("|" (type | attribute))*
|
| 20 |
+
selector ::= range | query range?
|
| 21 |
+
path ::= "/" selector ("/" selector)*
|
| 22 |
+
|
| 23 |
+
See the docstring of the epath() function.
|
| 24 |
+
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
__slots__ = ("_path", "_epath")
|
| 28 |
+
|
| 29 |
+
def __new__(cls, path):
|
| 30 |
+
"""Construct new EPath. """
|
| 31 |
+
if isinstance(path, EPath):
|
| 32 |
+
return path
|
| 33 |
+
|
| 34 |
+
if not path:
|
| 35 |
+
raise ValueError("empty EPath")
|
| 36 |
+
|
| 37 |
+
_path = path
|
| 38 |
+
|
| 39 |
+
if path[0] == '/':
|
| 40 |
+
path = path[1:]
|
| 41 |
+
else:
|
| 42 |
+
raise NotImplementedError("non-root EPath")
|
| 43 |
+
|
| 44 |
+
epath = []
|
| 45 |
+
|
| 46 |
+
for selector in path.split('/'):
|
| 47 |
+
selector = selector.strip()
|
| 48 |
+
|
| 49 |
+
if not selector:
|
| 50 |
+
raise ValueError("empty selector")
|
| 51 |
+
|
| 52 |
+
index = 0
|
| 53 |
+
|
| 54 |
+
for c in selector:
|
| 55 |
+
if c.isalnum() or c in ('_', '|', '?'):
|
| 56 |
+
index += 1
|
| 57 |
+
else:
|
| 58 |
+
break
|
| 59 |
+
|
| 60 |
+
attrs = []
|
| 61 |
+
types = []
|
| 62 |
+
|
| 63 |
+
if index:
|
| 64 |
+
elements = selector[:index]
|
| 65 |
+
selector = selector[index:]
|
| 66 |
+
|
| 67 |
+
for element in elements.split('|'):
|
| 68 |
+
element = element.strip()
|
| 69 |
+
|
| 70 |
+
if not element:
|
| 71 |
+
raise ValueError("empty element")
|
| 72 |
+
|
| 73 |
+
if element.endswith('?'):
|
| 74 |
+
attrs.append(element[:-1])
|
| 75 |
+
else:
|
| 76 |
+
types.append(element)
|
| 77 |
+
|
| 78 |
+
span = None
|
| 79 |
+
|
| 80 |
+
if selector == '*':
|
| 81 |
+
pass
|
| 82 |
+
else:
|
| 83 |
+
if selector.startswith('['):
|
| 84 |
+
try:
|
| 85 |
+
i = selector.index(']')
|
| 86 |
+
except ValueError:
|
| 87 |
+
raise ValueError("expected ']', got EOL")
|
| 88 |
+
|
| 89 |
+
_span, span = selector[1:i], []
|
| 90 |
+
|
| 91 |
+
if ':' not in _span:
|
| 92 |
+
span = int(_span)
|
| 93 |
+
else:
|
| 94 |
+
for elt in _span.split(':', 3):
|
| 95 |
+
if not elt:
|
| 96 |
+
span.append(None)
|
| 97 |
+
else:
|
| 98 |
+
span.append(int(elt))
|
| 99 |
+
|
| 100 |
+
span = slice(*span)
|
| 101 |
+
|
| 102 |
+
selector = selector[i + 1:]
|
| 103 |
+
|
| 104 |
+
if selector:
|
| 105 |
+
raise ValueError("trailing characters in selector")
|
| 106 |
+
|
| 107 |
+
epath.append((attrs, types, span))
|
| 108 |
+
|
| 109 |
+
obj = object.__new__(cls)
|
| 110 |
+
|
| 111 |
+
obj._path = _path
|
| 112 |
+
obj._epath = epath
|
| 113 |
+
|
| 114 |
+
return obj
|
| 115 |
+
|
| 116 |
+
def __repr__(self):
|
| 117 |
+
return "%s(%r)" % (self.__class__.__name__, self._path)
|
| 118 |
+
|
| 119 |
+
def _get_ordered_args(self, expr):
|
| 120 |
+
"""Sort ``expr.args`` using printing order. """
|
| 121 |
+
if expr.is_Add:
|
| 122 |
+
return expr.as_ordered_terms()
|
| 123 |
+
elif expr.is_Mul:
|
| 124 |
+
return expr.as_ordered_factors()
|
| 125 |
+
else:
|
| 126 |
+
return expr.args
|
| 127 |
+
|
| 128 |
+
def _hasattrs(self, expr, attrs):
|
| 129 |
+
"""Check if ``expr`` has any of ``attrs``. """
|
| 130 |
+
for attr in attrs:
|
| 131 |
+
if not hasattr(expr, attr):
|
| 132 |
+
return False
|
| 133 |
+
|
| 134 |
+
return True
|
| 135 |
+
|
| 136 |
+
def _hastypes(self, expr, types):
|
| 137 |
+
"""Check if ``expr`` is any of ``types``. """
|
| 138 |
+
_types = [ cls.__name__ for cls in expr.__class__.mro() ]
|
| 139 |
+
return bool(set(_types).intersection(types))
|
| 140 |
+
|
| 141 |
+
def _has(self, expr, attrs, types):
|
| 142 |
+
"""Apply ``_hasattrs`` and ``_hastypes`` to ``expr``. """
|
| 143 |
+
if not (attrs or types):
|
| 144 |
+
return True
|
| 145 |
+
|
| 146 |
+
if attrs and self._hasattrs(expr, attrs):
|
| 147 |
+
return True
|
| 148 |
+
|
| 149 |
+
if types and self._hastypes(expr, types):
|
| 150 |
+
return True
|
| 151 |
+
|
| 152 |
+
return False
|
| 153 |
+
|
| 154 |
+
def apply(self, expr, func, args=None, kwargs=None):
|
| 155 |
+
"""
|
| 156 |
+
Modify parts of an expression selected by a path.
|
| 157 |
+
|
| 158 |
+
Examples
|
| 159 |
+
========
|
| 160 |
+
|
| 161 |
+
>>> from sympy.simplify.epathtools import EPath
|
| 162 |
+
>>> from sympy import sin, cos, E
|
| 163 |
+
>>> from sympy.abc import x, y, z, t
|
| 164 |
+
|
| 165 |
+
>>> path = EPath("/*/[0]/Symbol")
|
| 166 |
+
>>> expr = [((x, 1), 2), ((3, y), z)]
|
| 167 |
+
|
| 168 |
+
>>> path.apply(expr, lambda expr: expr**2)
|
| 169 |
+
[((x**2, 1), 2), ((3, y**2), z)]
|
| 170 |
+
|
| 171 |
+
>>> path = EPath("/*/*/Symbol")
|
| 172 |
+
>>> expr = t + sin(x + 1) + cos(x + y + E)
|
| 173 |
+
|
| 174 |
+
>>> path.apply(expr, lambda expr: 2*expr)
|
| 175 |
+
t + sin(2*x + 1) + cos(2*x + 2*y + E)
|
| 176 |
+
|
| 177 |
+
"""
|
| 178 |
+
def _apply(path, expr, func):
|
| 179 |
+
if not path:
|
| 180 |
+
return func(expr)
|
| 181 |
+
else:
|
| 182 |
+
selector, path = path[0], path[1:]
|
| 183 |
+
attrs, types, span = selector
|
| 184 |
+
|
| 185 |
+
if isinstance(expr, Basic):
|
| 186 |
+
if not expr.is_Atom:
|
| 187 |
+
args, basic = self._get_ordered_args(expr), True
|
| 188 |
+
else:
|
| 189 |
+
return expr
|
| 190 |
+
elif hasattr(expr, '__iter__'):
|
| 191 |
+
args, basic = expr, False
|
| 192 |
+
else:
|
| 193 |
+
return expr
|
| 194 |
+
|
| 195 |
+
args = list(args)
|
| 196 |
+
|
| 197 |
+
if span is not None:
|
| 198 |
+
if isinstance(span, slice):
|
| 199 |
+
indices = range(*span.indices(len(args)))
|
| 200 |
+
else:
|
| 201 |
+
indices = [span]
|
| 202 |
+
else:
|
| 203 |
+
indices = range(len(args))
|
| 204 |
+
|
| 205 |
+
for i in indices:
|
| 206 |
+
try:
|
| 207 |
+
arg = args[i]
|
| 208 |
+
except IndexError:
|
| 209 |
+
continue
|
| 210 |
+
|
| 211 |
+
if self._has(arg, attrs, types):
|
| 212 |
+
args[i] = _apply(path, arg, func)
|
| 213 |
+
|
| 214 |
+
if basic:
|
| 215 |
+
return expr.func(*args)
|
| 216 |
+
else:
|
| 217 |
+
return expr.__class__(args)
|
| 218 |
+
|
| 219 |
+
_args, _kwargs = args or (), kwargs or {}
|
| 220 |
+
_func = lambda expr: func(expr, *_args, **_kwargs)
|
| 221 |
+
|
| 222 |
+
return _apply(self._epath, expr, _func)
|
| 223 |
+
|
| 224 |
+
def select(self, expr):
|
| 225 |
+
"""
|
| 226 |
+
Retrieve parts of an expression selected by a path.
|
| 227 |
+
|
| 228 |
+
Examples
|
| 229 |
+
========
|
| 230 |
+
|
| 231 |
+
>>> from sympy.simplify.epathtools import EPath
|
| 232 |
+
>>> from sympy import sin, cos, E
|
| 233 |
+
>>> from sympy.abc import x, y, z, t
|
| 234 |
+
|
| 235 |
+
>>> path = EPath("/*/[0]/Symbol")
|
| 236 |
+
>>> expr = [((x, 1), 2), ((3, y), z)]
|
| 237 |
+
|
| 238 |
+
>>> path.select(expr)
|
| 239 |
+
[x, y]
|
| 240 |
+
|
| 241 |
+
>>> path = EPath("/*/*/Symbol")
|
| 242 |
+
>>> expr = t + sin(x + 1) + cos(x + y + E)
|
| 243 |
+
|
| 244 |
+
>>> path.select(expr)
|
| 245 |
+
[x, x, y]
|
| 246 |
+
|
| 247 |
+
"""
|
| 248 |
+
result = []
|
| 249 |
+
|
| 250 |
+
def _select(path, expr):
|
| 251 |
+
if not path:
|
| 252 |
+
result.append(expr)
|
| 253 |
+
else:
|
| 254 |
+
selector, path = path[0], path[1:]
|
| 255 |
+
attrs, types, span = selector
|
| 256 |
+
|
| 257 |
+
if isinstance(expr, Basic):
|
| 258 |
+
args = self._get_ordered_args(expr)
|
| 259 |
+
elif hasattr(expr, '__iter__'):
|
| 260 |
+
args = expr
|
| 261 |
+
else:
|
| 262 |
+
return
|
| 263 |
+
|
| 264 |
+
if span is not None:
|
| 265 |
+
if isinstance(span, slice):
|
| 266 |
+
args = args[span]
|
| 267 |
+
else:
|
| 268 |
+
try:
|
| 269 |
+
args = [args[span]]
|
| 270 |
+
except IndexError:
|
| 271 |
+
return
|
| 272 |
+
|
| 273 |
+
for arg in args:
|
| 274 |
+
if self._has(arg, attrs, types):
|
| 275 |
+
_select(path, arg)
|
| 276 |
+
|
| 277 |
+
_select(self._epath, expr)
|
| 278 |
+
return result
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def epath(path, expr=None, func=None, args=None, kwargs=None):
|
| 282 |
+
r"""
|
| 283 |
+
Manipulate parts of an expression selected by a path.
|
| 284 |
+
|
| 285 |
+
Explanation
|
| 286 |
+
===========
|
| 287 |
+
|
| 288 |
+
This function allows to manipulate large nested expressions in single
|
| 289 |
+
line of code, utilizing techniques to those applied in XML processing
|
| 290 |
+
standards (e.g. XPath).
|
| 291 |
+
|
| 292 |
+
If ``func`` is ``None``, :func:`epath` retrieves elements selected by
|
| 293 |
+
the ``path``. Otherwise it applies ``func`` to each matching element.
|
| 294 |
+
|
| 295 |
+
Note that it is more efficient to create an EPath object and use the select
|
| 296 |
+
and apply methods of that object, since this will compile the path string
|
| 297 |
+
only once. This function should only be used as a convenient shortcut for
|
| 298 |
+
interactive use.
|
| 299 |
+
|
| 300 |
+
This is the supported syntax:
|
| 301 |
+
|
| 302 |
+
* select all: ``/*``
|
| 303 |
+
Equivalent of ``for arg in args:``.
|
| 304 |
+
* select slice: ``/[0]`` or ``/[1:5]`` or ``/[1:5:2]``
|
| 305 |
+
Supports standard Python's slice syntax.
|
| 306 |
+
* select by type: ``/list`` or ``/list|tuple``
|
| 307 |
+
Emulates ``isinstance()``.
|
| 308 |
+
* select by attribute: ``/__iter__?``
|
| 309 |
+
Emulates ``hasattr()``.
|
| 310 |
+
|
| 311 |
+
Parameters
|
| 312 |
+
==========
|
| 313 |
+
|
| 314 |
+
path : str | EPath
|
| 315 |
+
A path as a string or a compiled EPath.
|
| 316 |
+
expr : Basic | iterable
|
| 317 |
+
An expression or a container of expressions.
|
| 318 |
+
func : callable (optional)
|
| 319 |
+
A callable that will be applied to matching parts.
|
| 320 |
+
args : tuple (optional)
|
| 321 |
+
Additional positional arguments to ``func``.
|
| 322 |
+
kwargs : dict (optional)
|
| 323 |
+
Additional keyword arguments to ``func``.
|
| 324 |
+
|
| 325 |
+
Examples
|
| 326 |
+
========
|
| 327 |
+
|
| 328 |
+
>>> from sympy.simplify.epathtools import epath
|
| 329 |
+
>>> from sympy import sin, cos, E
|
| 330 |
+
>>> from sympy.abc import x, y, z, t
|
| 331 |
+
|
| 332 |
+
>>> path = "/*/[0]/Symbol"
|
| 333 |
+
>>> expr = [((x, 1), 2), ((3, y), z)]
|
| 334 |
+
|
| 335 |
+
>>> epath(path, expr)
|
| 336 |
+
[x, y]
|
| 337 |
+
>>> epath(path, expr, lambda expr: expr**2)
|
| 338 |
+
[((x**2, 1), 2), ((3, y**2), z)]
|
| 339 |
+
|
| 340 |
+
>>> path = "/*/*/Symbol"
|
| 341 |
+
>>> expr = t + sin(x + 1) + cos(x + y + E)
|
| 342 |
+
|
| 343 |
+
>>> epath(path, expr)
|
| 344 |
+
[x, x, y]
|
| 345 |
+
>>> epath(path, expr, lambda expr: 2*expr)
|
| 346 |
+
t + sin(2*x + 1) + cos(2*x + 2*y + E)
|
| 347 |
+
|
| 348 |
+
"""
|
| 349 |
+
_epath = EPath(path)
|
| 350 |
+
|
| 351 |
+
if expr is None:
|
| 352 |
+
return _epath
|
| 353 |
+
if func is None:
|
| 354 |
+
return _epath.select(expr)
|
| 355 |
+
else:
|
| 356 |
+
return _epath.apply(expr, func, args, kwargs)
|
lib/python3.12/site-packages/sympy/simplify/fu.py
ADDED
|
@@ -0,0 +1,2111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import defaultdict
|
| 2 |
+
|
| 3 |
+
from sympy.core.add import Add
|
| 4 |
+
from sympy.core.expr import Expr
|
| 5 |
+
from sympy.core.exprtools import Factors, gcd_terms, factor_terms
|
| 6 |
+
from sympy.core.function import expand_mul
|
| 7 |
+
from sympy.core.mul import Mul
|
| 8 |
+
from sympy.core.numbers import pi, I
|
| 9 |
+
from sympy.core.power import Pow
|
| 10 |
+
from sympy.core.singleton import S
|
| 11 |
+
from sympy.core.sorting import ordered
|
| 12 |
+
from sympy.core.symbol import Dummy
|
| 13 |
+
from sympy.core.sympify import sympify
|
| 14 |
+
from sympy.core.traversal import bottom_up
|
| 15 |
+
from sympy.functions.combinatorial.factorials import binomial
|
| 16 |
+
from sympy.functions.elementary.hyperbolic import (
|
| 17 |
+
cosh, sinh, tanh, coth, sech, csch, HyperbolicFunction)
|
| 18 |
+
from sympy.functions.elementary.trigonometric import (
|
| 19 |
+
cos, sin, tan, cot, sec, csc, sqrt, TrigonometricFunction)
|
| 20 |
+
from sympy.ntheory.factor_ import perfect_power
|
| 21 |
+
from sympy.polys.polytools import factor
|
| 22 |
+
from sympy.strategies.tree import greedy
|
| 23 |
+
from sympy.strategies.core import identity, debug
|
| 24 |
+
|
| 25 |
+
from sympy import SYMPY_DEBUG
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
# ================== Fu-like tools ===========================
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def TR0(rv):
|
| 32 |
+
"""Simplification of rational polynomials, trying to simplify
|
| 33 |
+
the expression, e.g. combine things like 3*x + 2*x, etc....
|
| 34 |
+
"""
|
| 35 |
+
# although it would be nice to use cancel, it doesn't work
|
| 36 |
+
# with noncommutatives
|
| 37 |
+
return rv.normal().factor().expand()
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def TR1(rv):
|
| 41 |
+
"""Replace sec, csc with 1/cos, 1/sin
|
| 42 |
+
|
| 43 |
+
Examples
|
| 44 |
+
========
|
| 45 |
+
|
| 46 |
+
>>> from sympy.simplify.fu import TR1, sec, csc
|
| 47 |
+
>>> from sympy.abc import x
|
| 48 |
+
>>> TR1(2*csc(x) + sec(x))
|
| 49 |
+
1/cos(x) + 2/sin(x)
|
| 50 |
+
"""
|
| 51 |
+
|
| 52 |
+
def f(rv):
|
| 53 |
+
if isinstance(rv, sec):
|
| 54 |
+
a = rv.args[0]
|
| 55 |
+
return S.One/cos(a)
|
| 56 |
+
elif isinstance(rv, csc):
|
| 57 |
+
a = rv.args[0]
|
| 58 |
+
return S.One/sin(a)
|
| 59 |
+
return rv
|
| 60 |
+
|
| 61 |
+
return bottom_up(rv, f)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def TR2(rv):
|
| 65 |
+
"""Replace tan and cot with sin/cos and cos/sin
|
| 66 |
+
|
| 67 |
+
Examples
|
| 68 |
+
========
|
| 69 |
+
|
| 70 |
+
>>> from sympy.simplify.fu import TR2
|
| 71 |
+
>>> from sympy.abc import x
|
| 72 |
+
>>> from sympy import tan, cot, sin, cos
|
| 73 |
+
>>> TR2(tan(x))
|
| 74 |
+
sin(x)/cos(x)
|
| 75 |
+
>>> TR2(cot(x))
|
| 76 |
+
cos(x)/sin(x)
|
| 77 |
+
>>> TR2(tan(tan(x) - sin(x)/cos(x)))
|
| 78 |
+
0
|
| 79 |
+
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
def f(rv):
|
| 83 |
+
if isinstance(rv, tan):
|
| 84 |
+
a = rv.args[0]
|
| 85 |
+
return sin(a)/cos(a)
|
| 86 |
+
elif isinstance(rv, cot):
|
| 87 |
+
a = rv.args[0]
|
| 88 |
+
return cos(a)/sin(a)
|
| 89 |
+
return rv
|
| 90 |
+
|
| 91 |
+
return bottom_up(rv, f)
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def TR2i(rv, half=False):
|
| 95 |
+
"""Converts ratios involving sin and cos as follows::
|
| 96 |
+
sin(x)/cos(x) -> tan(x)
|
| 97 |
+
sin(x)/(cos(x) + 1) -> tan(x/2) if half=True
|
| 98 |
+
|
| 99 |
+
Examples
|
| 100 |
+
========
|
| 101 |
+
|
| 102 |
+
>>> from sympy.simplify.fu import TR2i
|
| 103 |
+
>>> from sympy.abc import x, a
|
| 104 |
+
>>> from sympy import sin, cos
|
| 105 |
+
>>> TR2i(sin(x)/cos(x))
|
| 106 |
+
tan(x)
|
| 107 |
+
|
| 108 |
+
Powers of the numerator and denominator are also recognized
|
| 109 |
+
|
| 110 |
+
>>> TR2i(sin(x)**2/(cos(x) + 1)**2, half=True)
|
| 111 |
+
tan(x/2)**2
|
| 112 |
+
|
| 113 |
+
The transformation does not take place unless assumptions allow
|
| 114 |
+
(i.e. the base must be positive or the exponent must be an integer
|
| 115 |
+
for both numerator and denominator)
|
| 116 |
+
|
| 117 |
+
>>> TR2i(sin(x)**a/(cos(x) + 1)**a)
|
| 118 |
+
sin(x)**a/(cos(x) + 1)**a
|
| 119 |
+
|
| 120 |
+
"""
|
| 121 |
+
|
| 122 |
+
def f(rv):
|
| 123 |
+
if not rv.is_Mul:
|
| 124 |
+
return rv
|
| 125 |
+
|
| 126 |
+
n, d = rv.as_numer_denom()
|
| 127 |
+
if n.is_Atom or d.is_Atom:
|
| 128 |
+
return rv
|
| 129 |
+
|
| 130 |
+
def ok(k, e):
|
| 131 |
+
# initial filtering of factors
|
| 132 |
+
return (
|
| 133 |
+
(e.is_integer or k.is_positive) and (
|
| 134 |
+
k.func in (sin, cos) or (half and
|
| 135 |
+
k.is_Add and
|
| 136 |
+
len(k.args) >= 2 and
|
| 137 |
+
any(any(isinstance(ai, cos) or ai.is_Pow and ai.base is cos
|
| 138 |
+
for ai in Mul.make_args(a)) for a in k.args))))
|
| 139 |
+
|
| 140 |
+
n = n.as_powers_dict()
|
| 141 |
+
ndone = [(k, n.pop(k)) for k in list(n.keys()) if not ok(k, n[k])]
|
| 142 |
+
if not n:
|
| 143 |
+
return rv
|
| 144 |
+
|
| 145 |
+
d = d.as_powers_dict()
|
| 146 |
+
ddone = [(k, d.pop(k)) for k in list(d.keys()) if not ok(k, d[k])]
|
| 147 |
+
if not d:
|
| 148 |
+
return rv
|
| 149 |
+
|
| 150 |
+
# factoring if necessary
|
| 151 |
+
|
| 152 |
+
def factorize(d, ddone):
|
| 153 |
+
newk = []
|
| 154 |
+
for k in d:
|
| 155 |
+
if k.is_Add and len(k.args) > 1:
|
| 156 |
+
knew = factor(k) if half else factor_terms(k)
|
| 157 |
+
if knew != k:
|
| 158 |
+
newk.append((k, knew))
|
| 159 |
+
if newk:
|
| 160 |
+
for i, (k, knew) in enumerate(newk):
|
| 161 |
+
del d[k]
|
| 162 |
+
newk[i] = knew
|
| 163 |
+
newk = Mul(*newk).as_powers_dict()
|
| 164 |
+
for k in newk:
|
| 165 |
+
v = d[k] + newk[k]
|
| 166 |
+
if ok(k, v):
|
| 167 |
+
d[k] = v
|
| 168 |
+
else:
|
| 169 |
+
ddone.append((k, v))
|
| 170 |
+
del newk
|
| 171 |
+
factorize(n, ndone)
|
| 172 |
+
factorize(d, ddone)
|
| 173 |
+
|
| 174 |
+
# joining
|
| 175 |
+
t = []
|
| 176 |
+
for k in n:
|
| 177 |
+
if isinstance(k, sin):
|
| 178 |
+
a = cos(k.args[0], evaluate=False)
|
| 179 |
+
if a in d and d[a] == n[k]:
|
| 180 |
+
t.append(tan(k.args[0])**n[k])
|
| 181 |
+
n[k] = d[a] = None
|
| 182 |
+
elif half:
|
| 183 |
+
a1 = 1 + a
|
| 184 |
+
if a1 in d and d[a1] == n[k]:
|
| 185 |
+
t.append((tan(k.args[0]/2))**n[k])
|
| 186 |
+
n[k] = d[a1] = None
|
| 187 |
+
elif isinstance(k, cos):
|
| 188 |
+
a = sin(k.args[0], evaluate=False)
|
| 189 |
+
if a in d and d[a] == n[k]:
|
| 190 |
+
t.append(tan(k.args[0])**-n[k])
|
| 191 |
+
n[k] = d[a] = None
|
| 192 |
+
elif half and k.is_Add and k.args[0] is S.One and \
|
| 193 |
+
isinstance(k.args[1], cos):
|
| 194 |
+
a = sin(k.args[1].args[0], evaluate=False)
|
| 195 |
+
if a in d and d[a] == n[k] and (d[a].is_integer or \
|
| 196 |
+
a.is_positive):
|
| 197 |
+
t.append(tan(a.args[0]/2)**-n[k])
|
| 198 |
+
n[k] = d[a] = None
|
| 199 |
+
|
| 200 |
+
if t:
|
| 201 |
+
rv = Mul(*(t + [b**e for b, e in n.items() if e]))/\
|
| 202 |
+
Mul(*[b**e for b, e in d.items() if e])
|
| 203 |
+
rv *= Mul(*[b**e for b, e in ndone])/Mul(*[b**e for b, e in ddone])
|
| 204 |
+
|
| 205 |
+
return rv
|
| 206 |
+
|
| 207 |
+
return bottom_up(rv, f)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def TR3(rv):
|
| 211 |
+
"""Induced formula: example sin(-a) = -sin(a)
|
| 212 |
+
|
| 213 |
+
Examples
|
| 214 |
+
========
|
| 215 |
+
|
| 216 |
+
>>> from sympy.simplify.fu import TR3
|
| 217 |
+
>>> from sympy.abc import x, y
|
| 218 |
+
>>> from sympy import pi
|
| 219 |
+
>>> from sympy import cos
|
| 220 |
+
>>> TR3(cos(y - x*(y - x)))
|
| 221 |
+
cos(x*(x - y) + y)
|
| 222 |
+
>>> cos(pi/2 + x)
|
| 223 |
+
-sin(x)
|
| 224 |
+
>>> cos(30*pi/2 + x)
|
| 225 |
+
-cos(x)
|
| 226 |
+
|
| 227 |
+
"""
|
| 228 |
+
from sympy.simplify.simplify import signsimp
|
| 229 |
+
|
| 230 |
+
# Negative argument (already automatic for funcs like sin(-x) -> -sin(x)
|
| 231 |
+
# but more complicated expressions can use it, too). Also, trig angles
|
| 232 |
+
# between pi/4 and pi/2 are not reduced to an angle between 0 and pi/4.
|
| 233 |
+
# The following are automatically handled:
|
| 234 |
+
# Argument of type: pi/2 +/- angle
|
| 235 |
+
# Argument of type: pi +/- angle
|
| 236 |
+
# Argument of type : 2k*pi +/- angle
|
| 237 |
+
|
| 238 |
+
def f(rv):
|
| 239 |
+
if not isinstance(rv, TrigonometricFunction):
|
| 240 |
+
return rv
|
| 241 |
+
rv = rv.func(signsimp(rv.args[0]))
|
| 242 |
+
if not isinstance(rv, TrigonometricFunction):
|
| 243 |
+
return rv
|
| 244 |
+
if (rv.args[0] - S.Pi/4).is_positive is (S.Pi/2 - rv.args[0]).is_positive is True:
|
| 245 |
+
fmap = {cos: sin, sin: cos, tan: cot, cot: tan, sec: csc, csc: sec}
|
| 246 |
+
rv = fmap[type(rv)](S.Pi/2 - rv.args[0])
|
| 247 |
+
return rv
|
| 248 |
+
|
| 249 |
+
# touch numbers iside of trig functions to let them automatically update
|
| 250 |
+
rv = rv.replace(
|
| 251 |
+
lambda x: isinstance(x, TrigonometricFunction),
|
| 252 |
+
lambda x: x.replace(
|
| 253 |
+
lambda n: n.is_number and n.is_Mul,
|
| 254 |
+
lambda n: n.func(*n.args)))
|
| 255 |
+
|
| 256 |
+
return bottom_up(rv, f)
|
| 257 |
+
|
| 258 |
+
|
| 259 |
+
def TR4(rv):
|
| 260 |
+
"""Identify values of special angles.
|
| 261 |
+
|
| 262 |
+
a= 0 pi/6 pi/4 pi/3 pi/2
|
| 263 |
+
----------------------------------------------------
|
| 264 |
+
sin(a) 0 1/2 sqrt(2)/2 sqrt(3)/2 1
|
| 265 |
+
cos(a) 1 sqrt(3)/2 sqrt(2)/2 1/2 0
|
| 266 |
+
tan(a) 0 sqt(3)/3 1 sqrt(3) --
|
| 267 |
+
|
| 268 |
+
Examples
|
| 269 |
+
========
|
| 270 |
+
|
| 271 |
+
>>> from sympy import pi
|
| 272 |
+
>>> from sympy import cos, sin, tan, cot
|
| 273 |
+
>>> for s in (0, pi/6, pi/4, pi/3, pi/2):
|
| 274 |
+
... print('%s %s %s %s' % (cos(s), sin(s), tan(s), cot(s)))
|
| 275 |
+
...
|
| 276 |
+
1 0 0 zoo
|
| 277 |
+
sqrt(3)/2 1/2 sqrt(3)/3 sqrt(3)
|
| 278 |
+
sqrt(2)/2 sqrt(2)/2 1 1
|
| 279 |
+
1/2 sqrt(3)/2 sqrt(3) sqrt(3)/3
|
| 280 |
+
0 1 zoo 0
|
| 281 |
+
"""
|
| 282 |
+
# special values at 0, pi/6, pi/4, pi/3, pi/2 already handled
|
| 283 |
+
return rv.replace(
|
| 284 |
+
lambda x:
|
| 285 |
+
isinstance(x, TrigonometricFunction) and
|
| 286 |
+
(r:=x.args[0]/pi).is_Rational and r.q in (1, 2, 3, 4, 6),
|
| 287 |
+
lambda x:
|
| 288 |
+
x.func(x.args[0].func(*x.args[0].args)))
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def _TR56(rv, f, g, h, max, pow):
|
| 292 |
+
"""Helper for TR5 and TR6 to replace f**2 with h(g**2)
|
| 293 |
+
|
| 294 |
+
Options
|
| 295 |
+
=======
|
| 296 |
+
|
| 297 |
+
max : controls size of exponent that can appear on f
|
| 298 |
+
e.g. if max=4 then f**4 will be changed to h(g**2)**2.
|
| 299 |
+
pow : controls whether the exponent must be a perfect power of 2
|
| 300 |
+
e.g. if pow=True (and max >= 6) then f**6 will not be changed
|
| 301 |
+
but f**8 will be changed to h(g**2)**4
|
| 302 |
+
|
| 303 |
+
>>> from sympy.simplify.fu import _TR56 as T
|
| 304 |
+
>>> from sympy.abc import x
|
| 305 |
+
>>> from sympy import sin, cos
|
| 306 |
+
>>> h = lambda x: 1 - x
|
| 307 |
+
>>> T(sin(x)**3, sin, cos, h, 4, False)
|
| 308 |
+
(1 - cos(x)**2)*sin(x)
|
| 309 |
+
>>> T(sin(x)**6, sin, cos, h, 6, False)
|
| 310 |
+
(1 - cos(x)**2)**3
|
| 311 |
+
>>> T(sin(x)**6, sin, cos, h, 6, True)
|
| 312 |
+
sin(x)**6
|
| 313 |
+
>>> T(sin(x)**8, sin, cos, h, 10, True)
|
| 314 |
+
(1 - cos(x)**2)**4
|
| 315 |
+
"""
|
| 316 |
+
|
| 317 |
+
def _f(rv):
|
| 318 |
+
# I'm not sure if this transformation should target all even powers
|
| 319 |
+
# or only those expressible as powers of 2. Also, should it only
|
| 320 |
+
# make the changes in powers that appear in sums -- making an isolated
|
| 321 |
+
# change is not going to allow a simplification as far as I can tell.
|
| 322 |
+
if not (rv.is_Pow and rv.base.func == f):
|
| 323 |
+
return rv
|
| 324 |
+
if not rv.exp.is_real:
|
| 325 |
+
return rv
|
| 326 |
+
|
| 327 |
+
if (rv.exp < 0) == True:
|
| 328 |
+
return rv
|
| 329 |
+
if (rv.exp > max) == True:
|
| 330 |
+
return rv
|
| 331 |
+
if rv.exp == 1:
|
| 332 |
+
return rv
|
| 333 |
+
if rv.exp == 2:
|
| 334 |
+
return h(g(rv.base.args[0])**2)
|
| 335 |
+
else:
|
| 336 |
+
if rv.exp % 2 == 1:
|
| 337 |
+
e = rv.exp//2
|
| 338 |
+
return f(rv.base.args[0])*h(g(rv.base.args[0])**2)**e
|
| 339 |
+
elif rv.exp == 4:
|
| 340 |
+
e = 2
|
| 341 |
+
elif not pow:
|
| 342 |
+
if rv.exp % 2:
|
| 343 |
+
return rv
|
| 344 |
+
e = rv.exp//2
|
| 345 |
+
else:
|
| 346 |
+
p = perfect_power(rv.exp)
|
| 347 |
+
if not p:
|
| 348 |
+
return rv
|
| 349 |
+
e = rv.exp//2
|
| 350 |
+
return h(g(rv.base.args[0])**2)**e
|
| 351 |
+
|
| 352 |
+
return bottom_up(rv, _f)
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
def TR5(rv, max=4, pow=False):
|
| 356 |
+
"""Replacement of sin**2 with 1 - cos(x)**2.
|
| 357 |
+
|
| 358 |
+
See _TR56 docstring for advanced use of ``max`` and ``pow``.
|
| 359 |
+
|
| 360 |
+
Examples
|
| 361 |
+
========
|
| 362 |
+
|
| 363 |
+
>>> from sympy.simplify.fu import TR5
|
| 364 |
+
>>> from sympy.abc import x
|
| 365 |
+
>>> from sympy import sin
|
| 366 |
+
>>> TR5(sin(x)**2)
|
| 367 |
+
1 - cos(x)**2
|
| 368 |
+
>>> TR5(sin(x)**-2) # unchanged
|
| 369 |
+
sin(x)**(-2)
|
| 370 |
+
>>> TR5(sin(x)**4)
|
| 371 |
+
(1 - cos(x)**2)**2
|
| 372 |
+
"""
|
| 373 |
+
return _TR56(rv, sin, cos, lambda x: 1 - x, max=max, pow=pow)
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
def TR6(rv, max=4, pow=False):
|
| 377 |
+
"""Replacement of cos**2 with 1 - sin(x)**2.
|
| 378 |
+
|
| 379 |
+
See _TR56 docstring for advanced use of ``max`` and ``pow``.
|
| 380 |
+
|
| 381 |
+
Examples
|
| 382 |
+
========
|
| 383 |
+
|
| 384 |
+
>>> from sympy.simplify.fu import TR6
|
| 385 |
+
>>> from sympy.abc import x
|
| 386 |
+
>>> from sympy import cos
|
| 387 |
+
>>> TR6(cos(x)**2)
|
| 388 |
+
1 - sin(x)**2
|
| 389 |
+
>>> TR6(cos(x)**-2) #unchanged
|
| 390 |
+
cos(x)**(-2)
|
| 391 |
+
>>> TR6(cos(x)**4)
|
| 392 |
+
(1 - sin(x)**2)**2
|
| 393 |
+
"""
|
| 394 |
+
return _TR56(rv, cos, sin, lambda x: 1 - x, max=max, pow=pow)
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def TR7(rv):
|
| 398 |
+
"""Lowering the degree of cos(x)**2.
|
| 399 |
+
|
| 400 |
+
Examples
|
| 401 |
+
========
|
| 402 |
+
|
| 403 |
+
>>> from sympy.simplify.fu import TR7
|
| 404 |
+
>>> from sympy.abc import x
|
| 405 |
+
>>> from sympy import cos
|
| 406 |
+
>>> TR7(cos(x)**2)
|
| 407 |
+
cos(2*x)/2 + 1/2
|
| 408 |
+
>>> TR7(cos(x)**2 + 1)
|
| 409 |
+
cos(2*x)/2 + 3/2
|
| 410 |
+
|
| 411 |
+
"""
|
| 412 |
+
|
| 413 |
+
def f(rv):
|
| 414 |
+
if not (rv.is_Pow and rv.base.func == cos and rv.exp == 2):
|
| 415 |
+
return rv
|
| 416 |
+
return (1 + cos(2*rv.base.args[0]))/2
|
| 417 |
+
|
| 418 |
+
return bottom_up(rv, f)
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
def TR8(rv, first=True):
|
| 422 |
+
"""Converting products of ``cos`` and/or ``sin`` to a sum or
|
| 423 |
+
difference of ``cos`` and or ``sin`` terms.
|
| 424 |
+
|
| 425 |
+
Examples
|
| 426 |
+
========
|
| 427 |
+
|
| 428 |
+
>>> from sympy.simplify.fu import TR8
|
| 429 |
+
>>> from sympy import cos, sin
|
| 430 |
+
>>> TR8(cos(2)*cos(3))
|
| 431 |
+
cos(5)/2 + cos(1)/2
|
| 432 |
+
>>> TR8(cos(2)*sin(3))
|
| 433 |
+
sin(5)/2 + sin(1)/2
|
| 434 |
+
>>> TR8(sin(2)*sin(3))
|
| 435 |
+
-cos(5)/2 + cos(1)/2
|
| 436 |
+
"""
|
| 437 |
+
|
| 438 |
+
def f(rv):
|
| 439 |
+
if not (
|
| 440 |
+
rv.is_Mul or
|
| 441 |
+
rv.is_Pow and
|
| 442 |
+
rv.base.func in (cos, sin) and
|
| 443 |
+
(rv.exp.is_integer or rv.base.is_positive)):
|
| 444 |
+
return rv
|
| 445 |
+
|
| 446 |
+
if first:
|
| 447 |
+
n, d = [expand_mul(i) for i in rv.as_numer_denom()]
|
| 448 |
+
newn = TR8(n, first=False)
|
| 449 |
+
newd = TR8(d, first=False)
|
| 450 |
+
if newn != n or newd != d:
|
| 451 |
+
rv = gcd_terms(newn/newd)
|
| 452 |
+
if rv.is_Mul and rv.args[0].is_Rational and \
|
| 453 |
+
len(rv.args) == 2 and rv.args[1].is_Add:
|
| 454 |
+
rv = Mul(*rv.as_coeff_Mul())
|
| 455 |
+
return rv
|
| 456 |
+
|
| 457 |
+
args = {cos: [], sin: [], None: []}
|
| 458 |
+
for a in Mul.make_args(rv):
|
| 459 |
+
if a.func in (cos, sin):
|
| 460 |
+
args[type(a)].append(a.args[0])
|
| 461 |
+
elif (a.is_Pow and a.exp.is_Integer and a.exp > 0 and \
|
| 462 |
+
a.base.func in (cos, sin)):
|
| 463 |
+
# XXX this is ok but pathological expression could be handled
|
| 464 |
+
# more efficiently as in TRmorrie
|
| 465 |
+
args[type(a.base)].extend([a.base.args[0]]*a.exp)
|
| 466 |
+
else:
|
| 467 |
+
args[None].append(a)
|
| 468 |
+
c = args[cos]
|
| 469 |
+
s = args[sin]
|
| 470 |
+
if not (c and s or len(c) > 1 or len(s) > 1):
|
| 471 |
+
return rv
|
| 472 |
+
|
| 473 |
+
args = args[None]
|
| 474 |
+
n = min(len(c), len(s))
|
| 475 |
+
for i in range(n):
|
| 476 |
+
a1 = s.pop()
|
| 477 |
+
a2 = c.pop()
|
| 478 |
+
args.append((sin(a1 + a2) + sin(a1 - a2))/2)
|
| 479 |
+
while len(c) > 1:
|
| 480 |
+
a1 = c.pop()
|
| 481 |
+
a2 = c.pop()
|
| 482 |
+
args.append((cos(a1 + a2) + cos(a1 - a2))/2)
|
| 483 |
+
if c:
|
| 484 |
+
args.append(cos(c.pop()))
|
| 485 |
+
while len(s) > 1:
|
| 486 |
+
a1 = s.pop()
|
| 487 |
+
a2 = s.pop()
|
| 488 |
+
args.append((-cos(a1 + a2) + cos(a1 - a2))/2)
|
| 489 |
+
if s:
|
| 490 |
+
args.append(sin(s.pop()))
|
| 491 |
+
return TR8(expand_mul(Mul(*args)))
|
| 492 |
+
|
| 493 |
+
return bottom_up(rv, f)
|
| 494 |
+
|
| 495 |
+
|
| 496 |
+
def TR9(rv):
|
| 497 |
+
"""Sum of ``cos`` or ``sin`` terms as a product of ``cos`` or ``sin``.
|
| 498 |
+
|
| 499 |
+
Examples
|
| 500 |
+
========
|
| 501 |
+
|
| 502 |
+
>>> from sympy.simplify.fu import TR9
|
| 503 |
+
>>> from sympy import cos, sin
|
| 504 |
+
>>> TR9(cos(1) + cos(2))
|
| 505 |
+
2*cos(1/2)*cos(3/2)
|
| 506 |
+
>>> TR9(cos(1) + 2*sin(1) + 2*sin(2))
|
| 507 |
+
cos(1) + 4*sin(3/2)*cos(1/2)
|
| 508 |
+
|
| 509 |
+
If no change is made by TR9, no re-arrangement of the
|
| 510 |
+
expression will be made. For example, though factoring
|
| 511 |
+
of common term is attempted, if the factored expression
|
| 512 |
+
was not changed, the original expression will be returned:
|
| 513 |
+
|
| 514 |
+
>>> TR9(cos(3) + cos(3)*cos(2))
|
| 515 |
+
cos(3) + cos(2)*cos(3)
|
| 516 |
+
|
| 517 |
+
"""
|
| 518 |
+
|
| 519 |
+
def f(rv):
|
| 520 |
+
if not rv.is_Add:
|
| 521 |
+
return rv
|
| 522 |
+
|
| 523 |
+
def do(rv, first=True):
|
| 524 |
+
# cos(a)+/-cos(b) can be combined into a product of cosines and
|
| 525 |
+
# sin(a)+/-sin(b) can be combined into a product of cosine and
|
| 526 |
+
# sine.
|
| 527 |
+
#
|
| 528 |
+
# If there are more than two args, the pairs which "work" will
|
| 529 |
+
# have a gcd extractable and the remaining two terms will have
|
| 530 |
+
# the above structure -- all pairs must be checked to find the
|
| 531 |
+
# ones that work. args that don't have a common set of symbols
|
| 532 |
+
# are skipped since this doesn't lead to a simpler formula and
|
| 533 |
+
# also has the arbitrariness of combining, for example, the x
|
| 534 |
+
# and y term instead of the y and z term in something like
|
| 535 |
+
# cos(x) + cos(y) + cos(z).
|
| 536 |
+
|
| 537 |
+
if not rv.is_Add:
|
| 538 |
+
return rv
|
| 539 |
+
|
| 540 |
+
args = list(ordered(rv.args))
|
| 541 |
+
if len(args) != 2:
|
| 542 |
+
hit = False
|
| 543 |
+
for i in range(len(args)):
|
| 544 |
+
ai = args[i]
|
| 545 |
+
if ai is None:
|
| 546 |
+
continue
|
| 547 |
+
for j in range(i + 1, len(args)):
|
| 548 |
+
aj = args[j]
|
| 549 |
+
if aj is None:
|
| 550 |
+
continue
|
| 551 |
+
was = ai + aj
|
| 552 |
+
new = do(was)
|
| 553 |
+
if new != was:
|
| 554 |
+
args[i] = new # update in place
|
| 555 |
+
args[j] = None
|
| 556 |
+
hit = True
|
| 557 |
+
break # go to next i
|
| 558 |
+
if hit:
|
| 559 |
+
rv = Add(*[_f for _f in args if _f])
|
| 560 |
+
if rv.is_Add:
|
| 561 |
+
rv = do(rv)
|
| 562 |
+
|
| 563 |
+
return rv
|
| 564 |
+
|
| 565 |
+
# two-arg Add
|
| 566 |
+
split = trig_split(*args)
|
| 567 |
+
if not split:
|
| 568 |
+
return rv
|
| 569 |
+
gcd, n1, n2, a, b, iscos = split
|
| 570 |
+
|
| 571 |
+
# application of rule if possible
|
| 572 |
+
if iscos:
|
| 573 |
+
if n1 == n2:
|
| 574 |
+
return gcd*n1*2*cos((a + b)/2)*cos((a - b)/2)
|
| 575 |
+
if n1 < 0:
|
| 576 |
+
a, b = b, a
|
| 577 |
+
return -2*gcd*sin((a + b)/2)*sin((a - b)/2)
|
| 578 |
+
else:
|
| 579 |
+
if n1 == n2:
|
| 580 |
+
return gcd*n1*2*sin((a + b)/2)*cos((a - b)/2)
|
| 581 |
+
if n1 < 0:
|
| 582 |
+
a, b = b, a
|
| 583 |
+
return 2*gcd*cos((a + b)/2)*sin((a - b)/2)
|
| 584 |
+
|
| 585 |
+
return process_common_addends(rv, do) # DON'T sift by free symbols
|
| 586 |
+
|
| 587 |
+
return bottom_up(rv, f)
|
| 588 |
+
|
| 589 |
+
|
| 590 |
+
def TR10(rv, first=True):
|
| 591 |
+
"""Separate sums in ``cos`` and ``sin``.
|
| 592 |
+
|
| 593 |
+
Examples
|
| 594 |
+
========
|
| 595 |
+
|
| 596 |
+
>>> from sympy.simplify.fu import TR10
|
| 597 |
+
>>> from sympy.abc import a, b, c
|
| 598 |
+
>>> from sympy import cos, sin
|
| 599 |
+
>>> TR10(cos(a + b))
|
| 600 |
+
-sin(a)*sin(b) + cos(a)*cos(b)
|
| 601 |
+
>>> TR10(sin(a + b))
|
| 602 |
+
sin(a)*cos(b) + sin(b)*cos(a)
|
| 603 |
+
>>> TR10(sin(a + b + c))
|
| 604 |
+
(-sin(a)*sin(b) + cos(a)*cos(b))*sin(c) + \
|
| 605 |
+
(sin(a)*cos(b) + sin(b)*cos(a))*cos(c)
|
| 606 |
+
"""
|
| 607 |
+
|
| 608 |
+
def f(rv):
|
| 609 |
+
if rv.func not in (cos, sin):
|
| 610 |
+
return rv
|
| 611 |
+
|
| 612 |
+
f = rv.func
|
| 613 |
+
arg = rv.args[0]
|
| 614 |
+
if arg.is_Add:
|
| 615 |
+
if first:
|
| 616 |
+
args = list(ordered(arg.args))
|
| 617 |
+
else:
|
| 618 |
+
args = list(arg.args)
|
| 619 |
+
a = args.pop()
|
| 620 |
+
b = Add._from_args(args)
|
| 621 |
+
if b.is_Add:
|
| 622 |
+
if f == sin:
|
| 623 |
+
return sin(a)*TR10(cos(b), first=False) + \
|
| 624 |
+
cos(a)*TR10(sin(b), first=False)
|
| 625 |
+
else:
|
| 626 |
+
return cos(a)*TR10(cos(b), first=False) - \
|
| 627 |
+
sin(a)*TR10(sin(b), first=False)
|
| 628 |
+
else:
|
| 629 |
+
if f == sin:
|
| 630 |
+
return sin(a)*cos(b) + cos(a)*sin(b)
|
| 631 |
+
else:
|
| 632 |
+
return cos(a)*cos(b) - sin(a)*sin(b)
|
| 633 |
+
return rv
|
| 634 |
+
|
| 635 |
+
return bottom_up(rv, f)
|
| 636 |
+
|
| 637 |
+
|
| 638 |
+
def TR10i(rv):
|
| 639 |
+
"""Sum of products to function of sum.
|
| 640 |
+
|
| 641 |
+
Examples
|
| 642 |
+
========
|
| 643 |
+
|
| 644 |
+
>>> from sympy.simplify.fu import TR10i
|
| 645 |
+
>>> from sympy import cos, sin, sqrt
|
| 646 |
+
>>> from sympy.abc import x
|
| 647 |
+
|
| 648 |
+
>>> TR10i(cos(1)*cos(3) + sin(1)*sin(3))
|
| 649 |
+
cos(2)
|
| 650 |
+
>>> TR10i(cos(1)*sin(3) + sin(1)*cos(3) + cos(3))
|
| 651 |
+
cos(3) + sin(4)
|
| 652 |
+
>>> TR10i(sqrt(2)*cos(x)*x + sqrt(6)*sin(x)*x)
|
| 653 |
+
2*sqrt(2)*x*sin(x + pi/6)
|
| 654 |
+
|
| 655 |
+
"""
|
| 656 |
+
global _ROOT2, _ROOT3, _invROOT3
|
| 657 |
+
if _ROOT2 is None:
|
| 658 |
+
_roots()
|
| 659 |
+
|
| 660 |
+
def f(rv):
|
| 661 |
+
if not rv.is_Add:
|
| 662 |
+
return rv
|
| 663 |
+
|
| 664 |
+
def do(rv, first=True):
|
| 665 |
+
# args which can be expressed as A*(cos(a)*cos(b)+/-sin(a)*sin(b))
|
| 666 |
+
# or B*(cos(a)*sin(b)+/-cos(b)*sin(a)) can be combined into
|
| 667 |
+
# A*f(a+/-b) where f is either sin or cos.
|
| 668 |
+
#
|
| 669 |
+
# If there are more than two args, the pairs which "work" will have
|
| 670 |
+
# a gcd extractable and the remaining two terms will have the above
|
| 671 |
+
# structure -- all pairs must be checked to find the ones that
|
| 672 |
+
# work.
|
| 673 |
+
|
| 674 |
+
if not rv.is_Add:
|
| 675 |
+
return rv
|
| 676 |
+
|
| 677 |
+
args = list(ordered(rv.args))
|
| 678 |
+
if len(args) != 2:
|
| 679 |
+
hit = False
|
| 680 |
+
for i in range(len(args)):
|
| 681 |
+
ai = args[i]
|
| 682 |
+
if ai is None:
|
| 683 |
+
continue
|
| 684 |
+
for j in range(i + 1, len(args)):
|
| 685 |
+
aj = args[j]
|
| 686 |
+
if aj is None:
|
| 687 |
+
continue
|
| 688 |
+
was = ai + aj
|
| 689 |
+
new = do(was)
|
| 690 |
+
if new != was:
|
| 691 |
+
args[i] = new # update in place
|
| 692 |
+
args[j] = None
|
| 693 |
+
hit = True
|
| 694 |
+
break # go to next i
|
| 695 |
+
if hit:
|
| 696 |
+
rv = Add(*[_f for _f in args if _f])
|
| 697 |
+
if rv.is_Add:
|
| 698 |
+
rv = do(rv)
|
| 699 |
+
|
| 700 |
+
return rv
|
| 701 |
+
|
| 702 |
+
# two-arg Add
|
| 703 |
+
split = trig_split(*args, two=True)
|
| 704 |
+
if not split:
|
| 705 |
+
return rv
|
| 706 |
+
gcd, n1, n2, a, b, same = split
|
| 707 |
+
|
| 708 |
+
# identify and get c1 to be cos then apply rule if possible
|
| 709 |
+
if same: # coscos, sinsin
|
| 710 |
+
gcd = n1*gcd
|
| 711 |
+
if n1 == n2:
|
| 712 |
+
return gcd*cos(a - b)
|
| 713 |
+
return gcd*cos(a + b)
|
| 714 |
+
else: #cossin, cossin
|
| 715 |
+
gcd = n1*gcd
|
| 716 |
+
if n1 == n2:
|
| 717 |
+
return gcd*sin(a + b)
|
| 718 |
+
return gcd*sin(b - a)
|
| 719 |
+
|
| 720 |
+
rv = process_common_addends(
|
| 721 |
+
rv, do, lambda x: tuple(ordered(x.free_symbols)))
|
| 722 |
+
|
| 723 |
+
# need to check for inducible pairs in ratio of sqrt(3):1 that
|
| 724 |
+
# appeared in different lists when sorting by coefficient
|
| 725 |
+
while rv.is_Add:
|
| 726 |
+
byrad = defaultdict(list)
|
| 727 |
+
for a in rv.args:
|
| 728 |
+
hit = 0
|
| 729 |
+
if a.is_Mul:
|
| 730 |
+
for ai in a.args:
|
| 731 |
+
if ai.is_Pow and ai.exp is S.Half and \
|
| 732 |
+
ai.base.is_Integer:
|
| 733 |
+
byrad[ai].append(a)
|
| 734 |
+
hit = 1
|
| 735 |
+
break
|
| 736 |
+
if not hit:
|
| 737 |
+
byrad[S.One].append(a)
|
| 738 |
+
|
| 739 |
+
# no need to check all pairs -- just check for the onees
|
| 740 |
+
# that have the right ratio
|
| 741 |
+
args = []
|
| 742 |
+
for a in byrad:
|
| 743 |
+
for b in [_ROOT3*a, _invROOT3]:
|
| 744 |
+
if b in byrad:
|
| 745 |
+
for i in range(len(byrad[a])):
|
| 746 |
+
if byrad[a][i] is None:
|
| 747 |
+
continue
|
| 748 |
+
for j in range(len(byrad[b])):
|
| 749 |
+
if byrad[b][j] is None:
|
| 750 |
+
continue
|
| 751 |
+
was = Add(byrad[a][i] + byrad[b][j])
|
| 752 |
+
new = do(was)
|
| 753 |
+
if new != was:
|
| 754 |
+
args.append(new)
|
| 755 |
+
byrad[a][i] = None
|
| 756 |
+
byrad[b][j] = None
|
| 757 |
+
break
|
| 758 |
+
if args:
|
| 759 |
+
rv = Add(*(args + [Add(*[_f for _f in v if _f])
|
| 760 |
+
for v in byrad.values()]))
|
| 761 |
+
else:
|
| 762 |
+
rv = do(rv) # final pass to resolve any new inducible pairs
|
| 763 |
+
break
|
| 764 |
+
|
| 765 |
+
return rv
|
| 766 |
+
|
| 767 |
+
return bottom_up(rv, f)
|
| 768 |
+
|
| 769 |
+
|
| 770 |
+
def TR11(rv, base=None):
|
| 771 |
+
"""Function of double angle to product. The ``base`` argument can be used
|
| 772 |
+
to indicate what is the un-doubled argument, e.g. if 3*pi/7 is the base
|
| 773 |
+
then cosine and sine functions with argument 6*pi/7 will be replaced.
|
| 774 |
+
|
| 775 |
+
Examples
|
| 776 |
+
========
|
| 777 |
+
|
| 778 |
+
>>> from sympy.simplify.fu import TR11
|
| 779 |
+
>>> from sympy import cos, sin, pi
|
| 780 |
+
>>> from sympy.abc import x
|
| 781 |
+
>>> TR11(sin(2*x))
|
| 782 |
+
2*sin(x)*cos(x)
|
| 783 |
+
>>> TR11(cos(2*x))
|
| 784 |
+
-sin(x)**2 + cos(x)**2
|
| 785 |
+
>>> TR11(sin(4*x))
|
| 786 |
+
4*(-sin(x)**2 + cos(x)**2)*sin(x)*cos(x)
|
| 787 |
+
>>> TR11(sin(4*x/3))
|
| 788 |
+
4*(-sin(x/3)**2 + cos(x/3)**2)*sin(x/3)*cos(x/3)
|
| 789 |
+
|
| 790 |
+
If the arguments are simply integers, no change is made
|
| 791 |
+
unless a base is provided:
|
| 792 |
+
|
| 793 |
+
>>> TR11(cos(2))
|
| 794 |
+
cos(2)
|
| 795 |
+
>>> TR11(cos(4), 2)
|
| 796 |
+
-sin(2)**2 + cos(2)**2
|
| 797 |
+
|
| 798 |
+
There is a subtle issue here in that autosimplification will convert
|
| 799 |
+
some higher angles to lower angles
|
| 800 |
+
|
| 801 |
+
>>> cos(6*pi/7) + cos(3*pi/7)
|
| 802 |
+
-cos(pi/7) + cos(3*pi/7)
|
| 803 |
+
|
| 804 |
+
The 6*pi/7 angle is now pi/7 but can be targeted with TR11 by supplying
|
| 805 |
+
the 3*pi/7 base:
|
| 806 |
+
|
| 807 |
+
>>> TR11(_, 3*pi/7)
|
| 808 |
+
-sin(3*pi/7)**2 + cos(3*pi/7)**2 + cos(3*pi/7)
|
| 809 |
+
|
| 810 |
+
"""
|
| 811 |
+
|
| 812 |
+
def f(rv):
|
| 813 |
+
if rv.func not in (cos, sin):
|
| 814 |
+
return rv
|
| 815 |
+
|
| 816 |
+
if base:
|
| 817 |
+
f = rv.func
|
| 818 |
+
t = f(base*2)
|
| 819 |
+
co = S.One
|
| 820 |
+
if t.is_Mul:
|
| 821 |
+
co, t = t.as_coeff_Mul()
|
| 822 |
+
if t.func not in (cos, sin):
|
| 823 |
+
return rv
|
| 824 |
+
if rv.args[0] == t.args[0]:
|
| 825 |
+
c = cos(base)
|
| 826 |
+
s = sin(base)
|
| 827 |
+
if f is cos:
|
| 828 |
+
return (c**2 - s**2)/co
|
| 829 |
+
else:
|
| 830 |
+
return 2*c*s/co
|
| 831 |
+
return rv
|
| 832 |
+
|
| 833 |
+
elif not rv.args[0].is_Number:
|
| 834 |
+
# make a change if the leading coefficient's numerator is
|
| 835 |
+
# divisible by 2
|
| 836 |
+
c, m = rv.args[0].as_coeff_Mul(rational=True)
|
| 837 |
+
if c.p % 2 == 0:
|
| 838 |
+
arg = c.p//2*m/c.q
|
| 839 |
+
c = TR11(cos(arg))
|
| 840 |
+
s = TR11(sin(arg))
|
| 841 |
+
if rv.func == sin:
|
| 842 |
+
rv = 2*s*c
|
| 843 |
+
else:
|
| 844 |
+
rv = c**2 - s**2
|
| 845 |
+
return rv
|
| 846 |
+
|
| 847 |
+
return bottom_up(rv, f)
|
| 848 |
+
|
| 849 |
+
|
| 850 |
+
def _TR11(rv):
|
| 851 |
+
"""
|
| 852 |
+
Helper for TR11 to find half-arguments for sin in factors of
|
| 853 |
+
num/den that appear in cos or sin factors in the den/num.
|
| 854 |
+
|
| 855 |
+
Examples
|
| 856 |
+
========
|
| 857 |
+
|
| 858 |
+
>>> from sympy.simplify.fu import TR11, _TR11
|
| 859 |
+
>>> from sympy import cos, sin
|
| 860 |
+
>>> from sympy.abc import x
|
| 861 |
+
>>> TR11(sin(x/3)/(cos(x/6)))
|
| 862 |
+
sin(x/3)/cos(x/6)
|
| 863 |
+
>>> _TR11(sin(x/3)/(cos(x/6)))
|
| 864 |
+
2*sin(x/6)
|
| 865 |
+
>>> TR11(sin(x/6)/(sin(x/3)))
|
| 866 |
+
sin(x/6)/sin(x/3)
|
| 867 |
+
>>> _TR11(sin(x/6)/(sin(x/3)))
|
| 868 |
+
1/(2*cos(x/6))
|
| 869 |
+
|
| 870 |
+
"""
|
| 871 |
+
def f(rv):
|
| 872 |
+
if not isinstance(rv, Expr):
|
| 873 |
+
return rv
|
| 874 |
+
|
| 875 |
+
def sincos_args(flat):
|
| 876 |
+
# find arguments of sin and cos that
|
| 877 |
+
# appears as bases in args of flat
|
| 878 |
+
# and have Integer exponents
|
| 879 |
+
args = defaultdict(set)
|
| 880 |
+
for fi in Mul.make_args(flat):
|
| 881 |
+
b, e = fi.as_base_exp()
|
| 882 |
+
if e.is_Integer and e > 0:
|
| 883 |
+
if b.func in (cos, sin):
|
| 884 |
+
args[type(b)].add(b.args[0])
|
| 885 |
+
return args
|
| 886 |
+
num_args, den_args = map(sincos_args, rv.as_numer_denom())
|
| 887 |
+
def handle_match(rv, num_args, den_args):
|
| 888 |
+
# for arg in sin args of num_args, look for arg/2
|
| 889 |
+
# in den_args and pass this half-angle to TR11
|
| 890 |
+
# for handling in rv
|
| 891 |
+
for narg in num_args[sin]:
|
| 892 |
+
half = narg/2
|
| 893 |
+
if half in den_args[cos]:
|
| 894 |
+
func = cos
|
| 895 |
+
elif half in den_args[sin]:
|
| 896 |
+
func = sin
|
| 897 |
+
else:
|
| 898 |
+
continue
|
| 899 |
+
rv = TR11(rv, half)
|
| 900 |
+
den_args[func].remove(half)
|
| 901 |
+
return rv
|
| 902 |
+
# sin in num, sin or cos in den
|
| 903 |
+
rv = handle_match(rv, num_args, den_args)
|
| 904 |
+
# sin in den, sin or cos in num
|
| 905 |
+
rv = handle_match(rv, den_args, num_args)
|
| 906 |
+
return rv
|
| 907 |
+
|
| 908 |
+
return bottom_up(rv, f)
|
| 909 |
+
|
| 910 |
+
|
| 911 |
+
def TR12(rv, first=True):
|
| 912 |
+
"""Separate sums in ``tan``.
|
| 913 |
+
|
| 914 |
+
Examples
|
| 915 |
+
========
|
| 916 |
+
|
| 917 |
+
>>> from sympy.abc import x, y
|
| 918 |
+
>>> from sympy import tan
|
| 919 |
+
>>> from sympy.simplify.fu import TR12
|
| 920 |
+
>>> TR12(tan(x + y))
|
| 921 |
+
(tan(x) + tan(y))/(-tan(x)*tan(y) + 1)
|
| 922 |
+
"""
|
| 923 |
+
|
| 924 |
+
def f(rv):
|
| 925 |
+
if not rv.func == tan:
|
| 926 |
+
return rv
|
| 927 |
+
|
| 928 |
+
arg = rv.args[0]
|
| 929 |
+
if arg.is_Add:
|
| 930 |
+
if first:
|
| 931 |
+
args = list(ordered(arg.args))
|
| 932 |
+
else:
|
| 933 |
+
args = list(arg.args)
|
| 934 |
+
a = args.pop()
|
| 935 |
+
b = Add._from_args(args)
|
| 936 |
+
if b.is_Add:
|
| 937 |
+
tb = TR12(tan(b), first=False)
|
| 938 |
+
else:
|
| 939 |
+
tb = tan(b)
|
| 940 |
+
return (tan(a) + tb)/(1 - tan(a)*tb)
|
| 941 |
+
return rv
|
| 942 |
+
|
| 943 |
+
return bottom_up(rv, f)
|
| 944 |
+
|
| 945 |
+
|
| 946 |
+
def TR12i(rv):
|
| 947 |
+
"""Combine tan arguments as
|
| 948 |
+
(tan(y) + tan(x))/(tan(x)*tan(y) - 1) -> -tan(x + y).
|
| 949 |
+
|
| 950 |
+
Examples
|
| 951 |
+
========
|
| 952 |
+
|
| 953 |
+
>>> from sympy.simplify.fu import TR12i
|
| 954 |
+
>>> from sympy import tan
|
| 955 |
+
>>> from sympy.abc import a, b, c
|
| 956 |
+
>>> ta, tb, tc = [tan(i) for i in (a, b, c)]
|
| 957 |
+
>>> TR12i((ta + tb)/(-ta*tb + 1))
|
| 958 |
+
tan(a + b)
|
| 959 |
+
>>> TR12i((ta + tb)/(ta*tb - 1))
|
| 960 |
+
-tan(a + b)
|
| 961 |
+
>>> TR12i((-ta - tb)/(ta*tb - 1))
|
| 962 |
+
tan(a + b)
|
| 963 |
+
>>> eq = (ta + tb)/(-ta*tb + 1)**2*(-3*ta - 3*tc)/(2*(ta*tc - 1))
|
| 964 |
+
>>> TR12i(eq.expand())
|
| 965 |
+
-3*tan(a + b)*tan(a + c)/(2*(tan(a) + tan(b) - 1))
|
| 966 |
+
"""
|
| 967 |
+
def f(rv):
|
| 968 |
+
if not (rv.is_Add or rv.is_Mul or rv.is_Pow):
|
| 969 |
+
return rv
|
| 970 |
+
|
| 971 |
+
n, d = rv.as_numer_denom()
|
| 972 |
+
if not d.args or not n.args:
|
| 973 |
+
return rv
|
| 974 |
+
|
| 975 |
+
dok = {}
|
| 976 |
+
|
| 977 |
+
def ok(di):
|
| 978 |
+
m = as_f_sign_1(di)
|
| 979 |
+
if m:
|
| 980 |
+
g, f, s = m
|
| 981 |
+
if s is S.NegativeOne and f.is_Mul and len(f.args) == 2 and \
|
| 982 |
+
all(isinstance(fi, tan) for fi in f.args):
|
| 983 |
+
return g, f
|
| 984 |
+
|
| 985 |
+
d_args = list(Mul.make_args(d))
|
| 986 |
+
for i, di in enumerate(d_args):
|
| 987 |
+
m = ok(di)
|
| 988 |
+
if m:
|
| 989 |
+
g, t = m
|
| 990 |
+
s = Add(*[_.args[0] for _ in t.args])
|
| 991 |
+
dok[s] = S.One
|
| 992 |
+
d_args[i] = g
|
| 993 |
+
continue
|
| 994 |
+
if di.is_Add:
|
| 995 |
+
di = factor(di)
|
| 996 |
+
if di.is_Mul:
|
| 997 |
+
d_args.extend(di.args)
|
| 998 |
+
d_args[i] = S.One
|
| 999 |
+
elif di.is_Pow and (di.exp.is_integer or di.base.is_positive):
|
| 1000 |
+
m = ok(di.base)
|
| 1001 |
+
if m:
|
| 1002 |
+
g, t = m
|
| 1003 |
+
s = Add(*[_.args[0] for _ in t.args])
|
| 1004 |
+
dok[s] = di.exp
|
| 1005 |
+
d_args[i] = g**di.exp
|
| 1006 |
+
else:
|
| 1007 |
+
di = factor(di)
|
| 1008 |
+
if di.is_Mul:
|
| 1009 |
+
d_args.extend(di.args)
|
| 1010 |
+
d_args[i] = S.One
|
| 1011 |
+
if not dok:
|
| 1012 |
+
return rv
|
| 1013 |
+
|
| 1014 |
+
def ok(ni):
|
| 1015 |
+
if ni.is_Add and len(ni.args) == 2:
|
| 1016 |
+
a, b = ni.args
|
| 1017 |
+
if isinstance(a, tan) and isinstance(b, tan):
|
| 1018 |
+
return a, b
|
| 1019 |
+
n_args = list(Mul.make_args(factor_terms(n)))
|
| 1020 |
+
hit = False
|
| 1021 |
+
for i, ni in enumerate(n_args):
|
| 1022 |
+
m = ok(ni)
|
| 1023 |
+
if not m:
|
| 1024 |
+
m = ok(-ni)
|
| 1025 |
+
if m:
|
| 1026 |
+
n_args[i] = S.NegativeOne
|
| 1027 |
+
else:
|
| 1028 |
+
if ni.is_Add:
|
| 1029 |
+
ni = factor(ni)
|
| 1030 |
+
if ni.is_Mul:
|
| 1031 |
+
n_args.extend(ni.args)
|
| 1032 |
+
n_args[i] = S.One
|
| 1033 |
+
continue
|
| 1034 |
+
elif ni.is_Pow and (
|
| 1035 |
+
ni.exp.is_integer or ni.base.is_positive):
|
| 1036 |
+
m = ok(ni.base)
|
| 1037 |
+
if m:
|
| 1038 |
+
n_args[i] = S.One
|
| 1039 |
+
else:
|
| 1040 |
+
ni = factor(ni)
|
| 1041 |
+
if ni.is_Mul:
|
| 1042 |
+
n_args.extend(ni.args)
|
| 1043 |
+
n_args[i] = S.One
|
| 1044 |
+
continue
|
| 1045 |
+
else:
|
| 1046 |
+
continue
|
| 1047 |
+
else:
|
| 1048 |
+
n_args[i] = S.One
|
| 1049 |
+
hit = True
|
| 1050 |
+
s = Add(*[_.args[0] for _ in m])
|
| 1051 |
+
ed = dok[s]
|
| 1052 |
+
newed = ed.extract_additively(S.One)
|
| 1053 |
+
if newed is not None:
|
| 1054 |
+
if newed:
|
| 1055 |
+
dok[s] = newed
|
| 1056 |
+
else:
|
| 1057 |
+
dok.pop(s)
|
| 1058 |
+
n_args[i] *= -tan(s)
|
| 1059 |
+
|
| 1060 |
+
if hit:
|
| 1061 |
+
rv = Mul(*n_args)/Mul(*d_args)/Mul(*[(Add(*[
|
| 1062 |
+
tan(a) for a in i.args]) - 1)**e for i, e in dok.items()])
|
| 1063 |
+
|
| 1064 |
+
return rv
|
| 1065 |
+
|
| 1066 |
+
return bottom_up(rv, f)
|
| 1067 |
+
|
| 1068 |
+
|
| 1069 |
+
def TR13(rv):
|
| 1070 |
+
"""Change products of ``tan`` or ``cot``.
|
| 1071 |
+
|
| 1072 |
+
Examples
|
| 1073 |
+
========
|
| 1074 |
+
|
| 1075 |
+
>>> from sympy.simplify.fu import TR13
|
| 1076 |
+
>>> from sympy import tan, cot
|
| 1077 |
+
>>> TR13(tan(3)*tan(2))
|
| 1078 |
+
-tan(2)/tan(5) - tan(3)/tan(5) + 1
|
| 1079 |
+
>>> TR13(cot(3)*cot(2))
|
| 1080 |
+
cot(2)*cot(5) + 1 + cot(3)*cot(5)
|
| 1081 |
+
"""
|
| 1082 |
+
|
| 1083 |
+
def f(rv):
|
| 1084 |
+
if not rv.is_Mul:
|
| 1085 |
+
return rv
|
| 1086 |
+
|
| 1087 |
+
# XXX handle products of powers? or let power-reducing handle it?
|
| 1088 |
+
args = {tan: [], cot: [], None: []}
|
| 1089 |
+
for a in Mul.make_args(rv):
|
| 1090 |
+
if a.func in (tan, cot):
|
| 1091 |
+
args[type(a)].append(a.args[0])
|
| 1092 |
+
else:
|
| 1093 |
+
args[None].append(a)
|
| 1094 |
+
t = args[tan]
|
| 1095 |
+
c = args[cot]
|
| 1096 |
+
if len(t) < 2 and len(c) < 2:
|
| 1097 |
+
return rv
|
| 1098 |
+
args = args[None]
|
| 1099 |
+
while len(t) > 1:
|
| 1100 |
+
t1 = t.pop()
|
| 1101 |
+
t2 = t.pop()
|
| 1102 |
+
args.append(1 - (tan(t1)/tan(t1 + t2) + tan(t2)/tan(t1 + t2)))
|
| 1103 |
+
if t:
|
| 1104 |
+
args.append(tan(t.pop()))
|
| 1105 |
+
while len(c) > 1:
|
| 1106 |
+
t1 = c.pop()
|
| 1107 |
+
t2 = c.pop()
|
| 1108 |
+
args.append(1 + cot(t1)*cot(t1 + t2) + cot(t2)*cot(t1 + t2))
|
| 1109 |
+
if c:
|
| 1110 |
+
args.append(cot(c.pop()))
|
| 1111 |
+
return Mul(*args)
|
| 1112 |
+
|
| 1113 |
+
return bottom_up(rv, f)
|
| 1114 |
+
|
| 1115 |
+
|
| 1116 |
+
def TRmorrie(rv):
|
| 1117 |
+
"""Returns cos(x)*cos(2*x)*...*cos(2**(k-1)*x) -> sin(2**k*x)/(2**k*sin(x))
|
| 1118 |
+
|
| 1119 |
+
Examples
|
| 1120 |
+
========
|
| 1121 |
+
|
| 1122 |
+
>>> from sympy.simplify.fu import TRmorrie, TR8, TR3
|
| 1123 |
+
>>> from sympy.abc import x
|
| 1124 |
+
>>> from sympy import Mul, cos, pi
|
| 1125 |
+
>>> TRmorrie(cos(x)*cos(2*x))
|
| 1126 |
+
sin(4*x)/(4*sin(x))
|
| 1127 |
+
>>> TRmorrie(7*Mul(*[cos(x) for x in range(10)]))
|
| 1128 |
+
7*sin(12)*sin(16)*cos(5)*cos(7)*cos(9)/(64*sin(1)*sin(3))
|
| 1129 |
+
|
| 1130 |
+
Sometimes autosimplification will cause a power to be
|
| 1131 |
+
not recognized. e.g. in the following, cos(4*pi/7) automatically
|
| 1132 |
+
simplifies to -cos(3*pi/7) so only 2 of the 3 terms are
|
| 1133 |
+
recognized:
|
| 1134 |
+
|
| 1135 |
+
>>> TRmorrie(cos(pi/7)*cos(2*pi/7)*cos(4*pi/7))
|
| 1136 |
+
-sin(3*pi/7)*cos(3*pi/7)/(4*sin(pi/7))
|
| 1137 |
+
|
| 1138 |
+
A touch by TR8 resolves the expression to a Rational
|
| 1139 |
+
|
| 1140 |
+
>>> TR8(_)
|
| 1141 |
+
-1/8
|
| 1142 |
+
|
| 1143 |
+
In this case, if eq is unsimplified, the answer is obtained
|
| 1144 |
+
directly:
|
| 1145 |
+
|
| 1146 |
+
>>> eq = cos(pi/9)*cos(2*pi/9)*cos(3*pi/9)*cos(4*pi/9)
|
| 1147 |
+
>>> TRmorrie(eq)
|
| 1148 |
+
1/16
|
| 1149 |
+
|
| 1150 |
+
But if angles are made canonical with TR3 then the answer
|
| 1151 |
+
is not simplified without further work:
|
| 1152 |
+
|
| 1153 |
+
>>> TR3(eq)
|
| 1154 |
+
sin(pi/18)*cos(pi/9)*cos(2*pi/9)/2
|
| 1155 |
+
>>> TRmorrie(_)
|
| 1156 |
+
sin(pi/18)*sin(4*pi/9)/(8*sin(pi/9))
|
| 1157 |
+
>>> TR8(_)
|
| 1158 |
+
cos(7*pi/18)/(16*sin(pi/9))
|
| 1159 |
+
>>> TR3(_)
|
| 1160 |
+
1/16
|
| 1161 |
+
|
| 1162 |
+
The original expression would have resolve to 1/16 directly with TR8,
|
| 1163 |
+
however:
|
| 1164 |
+
|
| 1165 |
+
>>> TR8(eq)
|
| 1166 |
+
1/16
|
| 1167 |
+
|
| 1168 |
+
References
|
| 1169 |
+
==========
|
| 1170 |
+
|
| 1171 |
+
.. [1] https://en.wikipedia.org/wiki/Morrie%27s_law
|
| 1172 |
+
|
| 1173 |
+
"""
|
| 1174 |
+
|
| 1175 |
+
def f(rv, first=True):
|
| 1176 |
+
if not rv.is_Mul:
|
| 1177 |
+
return rv
|
| 1178 |
+
if first:
|
| 1179 |
+
n, d = rv.as_numer_denom()
|
| 1180 |
+
return f(n, 0)/f(d, 0)
|
| 1181 |
+
|
| 1182 |
+
args = defaultdict(list)
|
| 1183 |
+
coss = {}
|
| 1184 |
+
other = []
|
| 1185 |
+
for c in rv.args:
|
| 1186 |
+
b, e = c.as_base_exp()
|
| 1187 |
+
if e.is_Integer and isinstance(b, cos):
|
| 1188 |
+
co, a = b.args[0].as_coeff_Mul()
|
| 1189 |
+
args[a].append(co)
|
| 1190 |
+
coss[b] = e
|
| 1191 |
+
else:
|
| 1192 |
+
other.append(c)
|
| 1193 |
+
|
| 1194 |
+
new = []
|
| 1195 |
+
for a in args:
|
| 1196 |
+
c = args[a]
|
| 1197 |
+
c.sort()
|
| 1198 |
+
while c:
|
| 1199 |
+
k = 0
|
| 1200 |
+
cc = ci = c[0]
|
| 1201 |
+
while cc in c:
|
| 1202 |
+
k += 1
|
| 1203 |
+
cc *= 2
|
| 1204 |
+
if k > 1:
|
| 1205 |
+
newarg = sin(2**k*ci*a)/2**k/sin(ci*a)
|
| 1206 |
+
# see how many times this can be taken
|
| 1207 |
+
take = None
|
| 1208 |
+
ccs = []
|
| 1209 |
+
for i in range(k):
|
| 1210 |
+
cc /= 2
|
| 1211 |
+
key = cos(a*cc, evaluate=False)
|
| 1212 |
+
ccs.append(cc)
|
| 1213 |
+
take = min(coss[key], take or coss[key])
|
| 1214 |
+
# update exponent counts
|
| 1215 |
+
for i in range(k):
|
| 1216 |
+
cc = ccs.pop()
|
| 1217 |
+
key = cos(a*cc, evaluate=False)
|
| 1218 |
+
coss[key] -= take
|
| 1219 |
+
if not coss[key]:
|
| 1220 |
+
c.remove(cc)
|
| 1221 |
+
new.append(newarg**take)
|
| 1222 |
+
else:
|
| 1223 |
+
b = cos(c.pop(0)*a)
|
| 1224 |
+
other.append(b**coss[b])
|
| 1225 |
+
|
| 1226 |
+
if new:
|
| 1227 |
+
rv = Mul(*(new + other + [
|
| 1228 |
+
cos(k*a, evaluate=False) for a in args for k in args[a]]))
|
| 1229 |
+
|
| 1230 |
+
return rv
|
| 1231 |
+
|
| 1232 |
+
return bottom_up(rv, f)
|
| 1233 |
+
|
| 1234 |
+
|
| 1235 |
+
def TR14(rv, first=True):
|
| 1236 |
+
"""Convert factored powers of sin and cos identities into simpler
|
| 1237 |
+
expressions.
|
| 1238 |
+
|
| 1239 |
+
Examples
|
| 1240 |
+
========
|
| 1241 |
+
|
| 1242 |
+
>>> from sympy.simplify.fu import TR14
|
| 1243 |
+
>>> from sympy.abc import x, y
|
| 1244 |
+
>>> from sympy import cos, sin
|
| 1245 |
+
>>> TR14((cos(x) - 1)*(cos(x) + 1))
|
| 1246 |
+
-sin(x)**2
|
| 1247 |
+
>>> TR14((sin(x) - 1)*(sin(x) + 1))
|
| 1248 |
+
-cos(x)**2
|
| 1249 |
+
>>> p1 = (cos(x) + 1)*(cos(x) - 1)
|
| 1250 |
+
>>> p2 = (cos(y) - 1)*2*(cos(y) + 1)
|
| 1251 |
+
>>> p3 = (3*(cos(y) - 1))*(3*(cos(y) + 1))
|
| 1252 |
+
>>> TR14(p1*p2*p3*(x - 1))
|
| 1253 |
+
-18*(x - 1)*sin(x)**2*sin(y)**4
|
| 1254 |
+
|
| 1255 |
+
"""
|
| 1256 |
+
|
| 1257 |
+
def f(rv):
|
| 1258 |
+
if not rv.is_Mul:
|
| 1259 |
+
return rv
|
| 1260 |
+
|
| 1261 |
+
if first:
|
| 1262 |
+
# sort them by location in numerator and denominator
|
| 1263 |
+
# so the code below can just deal with positive exponents
|
| 1264 |
+
n, d = rv.as_numer_denom()
|
| 1265 |
+
if d is not S.One:
|
| 1266 |
+
newn = TR14(n, first=False)
|
| 1267 |
+
newd = TR14(d, first=False)
|
| 1268 |
+
if newn != n or newd != d:
|
| 1269 |
+
rv = newn/newd
|
| 1270 |
+
return rv
|
| 1271 |
+
|
| 1272 |
+
other = []
|
| 1273 |
+
process = []
|
| 1274 |
+
for a in rv.args:
|
| 1275 |
+
if a.is_Pow:
|
| 1276 |
+
b, e = a.as_base_exp()
|
| 1277 |
+
if not (e.is_integer or b.is_positive):
|
| 1278 |
+
other.append(a)
|
| 1279 |
+
continue
|
| 1280 |
+
a = b
|
| 1281 |
+
else:
|
| 1282 |
+
e = S.One
|
| 1283 |
+
m = as_f_sign_1(a)
|
| 1284 |
+
if not m or m[1].func not in (cos, sin):
|
| 1285 |
+
if e is S.One:
|
| 1286 |
+
other.append(a)
|
| 1287 |
+
else:
|
| 1288 |
+
other.append(a**e)
|
| 1289 |
+
continue
|
| 1290 |
+
g, f, si = m
|
| 1291 |
+
process.append((g, e.is_Number, e, f, si, a))
|
| 1292 |
+
|
| 1293 |
+
# sort them to get like terms next to each other
|
| 1294 |
+
process = list(ordered(process))
|
| 1295 |
+
|
| 1296 |
+
# keep track of whether there was any change
|
| 1297 |
+
nother = len(other)
|
| 1298 |
+
|
| 1299 |
+
# access keys
|
| 1300 |
+
keys = (g, t, e, f, si, a) = list(range(6))
|
| 1301 |
+
|
| 1302 |
+
while process:
|
| 1303 |
+
A = process.pop(0)
|
| 1304 |
+
if process:
|
| 1305 |
+
B = process[0]
|
| 1306 |
+
|
| 1307 |
+
if A[e].is_Number and B[e].is_Number:
|
| 1308 |
+
# both exponents are numbers
|
| 1309 |
+
if A[f] == B[f]:
|
| 1310 |
+
if A[si] != B[si]:
|
| 1311 |
+
B = process.pop(0)
|
| 1312 |
+
take = min(A[e], B[e])
|
| 1313 |
+
|
| 1314 |
+
# reinsert any remainder
|
| 1315 |
+
# the B will likely sort after A so check it first
|
| 1316 |
+
if B[e] != take:
|
| 1317 |
+
rem = [B[i] for i in keys]
|
| 1318 |
+
rem[e] -= take
|
| 1319 |
+
process.insert(0, rem)
|
| 1320 |
+
elif A[e] != take:
|
| 1321 |
+
rem = [A[i] for i in keys]
|
| 1322 |
+
rem[e] -= take
|
| 1323 |
+
process.insert(0, rem)
|
| 1324 |
+
|
| 1325 |
+
if isinstance(A[f], cos):
|
| 1326 |
+
t = sin
|
| 1327 |
+
else:
|
| 1328 |
+
t = cos
|
| 1329 |
+
other.append((-A[g]*B[g]*t(A[f].args[0])**2)**take)
|
| 1330 |
+
continue
|
| 1331 |
+
|
| 1332 |
+
elif A[e] == B[e]:
|
| 1333 |
+
# both exponents are equal symbols
|
| 1334 |
+
if A[f] == B[f]:
|
| 1335 |
+
if A[si] != B[si]:
|
| 1336 |
+
B = process.pop(0)
|
| 1337 |
+
take = A[e]
|
| 1338 |
+
if isinstance(A[f], cos):
|
| 1339 |
+
t = sin
|
| 1340 |
+
else:
|
| 1341 |
+
t = cos
|
| 1342 |
+
other.append((-A[g]*B[g]*t(A[f].args[0])**2)**take)
|
| 1343 |
+
continue
|
| 1344 |
+
|
| 1345 |
+
# either we are done or neither condition above applied
|
| 1346 |
+
other.append(A[a]**A[e])
|
| 1347 |
+
|
| 1348 |
+
if len(other) != nother:
|
| 1349 |
+
rv = Mul(*other)
|
| 1350 |
+
|
| 1351 |
+
return rv
|
| 1352 |
+
|
| 1353 |
+
return bottom_up(rv, f)
|
| 1354 |
+
|
| 1355 |
+
|
| 1356 |
+
def TR15(rv, max=4, pow=False):
|
| 1357 |
+
"""Convert sin(x)**-2 to 1 + cot(x)**2.
|
| 1358 |
+
|
| 1359 |
+
See _TR56 docstring for advanced use of ``max`` and ``pow``.
|
| 1360 |
+
|
| 1361 |
+
Examples
|
| 1362 |
+
========
|
| 1363 |
+
|
| 1364 |
+
>>> from sympy.simplify.fu import TR15
|
| 1365 |
+
>>> from sympy.abc import x
|
| 1366 |
+
>>> from sympy import sin
|
| 1367 |
+
>>> TR15(1 - 1/sin(x)**2)
|
| 1368 |
+
-cot(x)**2
|
| 1369 |
+
|
| 1370 |
+
"""
|
| 1371 |
+
|
| 1372 |
+
def f(rv):
|
| 1373 |
+
if not (isinstance(rv, Pow) and isinstance(rv.base, sin)):
|
| 1374 |
+
return rv
|
| 1375 |
+
|
| 1376 |
+
e = rv.exp
|
| 1377 |
+
if e % 2 == 1:
|
| 1378 |
+
return TR15(rv.base**(e + 1))/rv.base
|
| 1379 |
+
|
| 1380 |
+
ia = 1/rv
|
| 1381 |
+
a = _TR56(ia, sin, cot, lambda x: 1 + x, max=max, pow=pow)
|
| 1382 |
+
if a != ia:
|
| 1383 |
+
rv = a
|
| 1384 |
+
return rv
|
| 1385 |
+
|
| 1386 |
+
return bottom_up(rv, f)
|
| 1387 |
+
|
| 1388 |
+
|
| 1389 |
+
def TR16(rv, max=4, pow=False):
|
| 1390 |
+
"""Convert cos(x)**-2 to 1 + tan(x)**2.
|
| 1391 |
+
|
| 1392 |
+
See _TR56 docstring for advanced use of ``max`` and ``pow``.
|
| 1393 |
+
|
| 1394 |
+
Examples
|
| 1395 |
+
========
|
| 1396 |
+
|
| 1397 |
+
>>> from sympy.simplify.fu import TR16
|
| 1398 |
+
>>> from sympy.abc import x
|
| 1399 |
+
>>> from sympy import cos
|
| 1400 |
+
>>> TR16(1 - 1/cos(x)**2)
|
| 1401 |
+
-tan(x)**2
|
| 1402 |
+
|
| 1403 |
+
"""
|
| 1404 |
+
|
| 1405 |
+
def f(rv):
|
| 1406 |
+
if not (isinstance(rv, Pow) and isinstance(rv.base, cos)):
|
| 1407 |
+
return rv
|
| 1408 |
+
|
| 1409 |
+
e = rv.exp
|
| 1410 |
+
if e % 2 == 1:
|
| 1411 |
+
return TR15(rv.base**(e + 1))/rv.base
|
| 1412 |
+
|
| 1413 |
+
ia = 1/rv
|
| 1414 |
+
a = _TR56(ia, cos, tan, lambda x: 1 + x, max=max, pow=pow)
|
| 1415 |
+
if a != ia:
|
| 1416 |
+
rv = a
|
| 1417 |
+
return rv
|
| 1418 |
+
|
| 1419 |
+
return bottom_up(rv, f)
|
| 1420 |
+
|
| 1421 |
+
|
| 1422 |
+
def TR111(rv):
|
| 1423 |
+
"""Convert f(x)**-i to g(x)**i where either ``i`` is an integer
|
| 1424 |
+
or the base is positive and f, g are: tan, cot; sin, csc; or cos, sec.
|
| 1425 |
+
|
| 1426 |
+
Examples
|
| 1427 |
+
========
|
| 1428 |
+
|
| 1429 |
+
>>> from sympy.simplify.fu import TR111
|
| 1430 |
+
>>> from sympy.abc import x
|
| 1431 |
+
>>> from sympy import tan
|
| 1432 |
+
>>> TR111(1 - 1/tan(x)**2)
|
| 1433 |
+
1 - cot(x)**2
|
| 1434 |
+
|
| 1435 |
+
"""
|
| 1436 |
+
|
| 1437 |
+
def f(rv):
|
| 1438 |
+
if not (
|
| 1439 |
+
isinstance(rv, Pow) and
|
| 1440 |
+
(rv.base.is_positive or rv.exp.is_integer and rv.exp.is_negative)):
|
| 1441 |
+
return rv
|
| 1442 |
+
|
| 1443 |
+
if isinstance(rv.base, tan):
|
| 1444 |
+
return cot(rv.base.args[0])**-rv.exp
|
| 1445 |
+
elif isinstance(rv.base, sin):
|
| 1446 |
+
return csc(rv.base.args[0])**-rv.exp
|
| 1447 |
+
elif isinstance(rv.base, cos):
|
| 1448 |
+
return sec(rv.base.args[0])**-rv.exp
|
| 1449 |
+
return rv
|
| 1450 |
+
|
| 1451 |
+
return bottom_up(rv, f)
|
| 1452 |
+
|
| 1453 |
+
|
| 1454 |
+
def TR22(rv, max=4, pow=False):
|
| 1455 |
+
"""Convert tan(x)**2 to sec(x)**2 - 1 and cot(x)**2 to csc(x)**2 - 1.
|
| 1456 |
+
|
| 1457 |
+
See _TR56 docstring for advanced use of ``max`` and ``pow``.
|
| 1458 |
+
|
| 1459 |
+
Examples
|
| 1460 |
+
========
|
| 1461 |
+
|
| 1462 |
+
>>> from sympy.simplify.fu import TR22
|
| 1463 |
+
>>> from sympy.abc import x
|
| 1464 |
+
>>> from sympy import tan, cot
|
| 1465 |
+
>>> TR22(1 + tan(x)**2)
|
| 1466 |
+
sec(x)**2
|
| 1467 |
+
>>> TR22(1 + cot(x)**2)
|
| 1468 |
+
csc(x)**2
|
| 1469 |
+
|
| 1470 |
+
"""
|
| 1471 |
+
|
| 1472 |
+
def f(rv):
|
| 1473 |
+
if not (isinstance(rv, Pow) and rv.base.func in (cot, tan)):
|
| 1474 |
+
return rv
|
| 1475 |
+
|
| 1476 |
+
rv = _TR56(rv, tan, sec, lambda x: x - 1, max=max, pow=pow)
|
| 1477 |
+
rv = _TR56(rv, cot, csc, lambda x: x - 1, max=max, pow=pow)
|
| 1478 |
+
return rv
|
| 1479 |
+
|
| 1480 |
+
return bottom_up(rv, f)
|
| 1481 |
+
|
| 1482 |
+
|
| 1483 |
+
def TRpower(rv):
|
| 1484 |
+
"""Convert sin(x)**n and cos(x)**n with positive n to sums.
|
| 1485 |
+
|
| 1486 |
+
Examples
|
| 1487 |
+
========
|
| 1488 |
+
|
| 1489 |
+
>>> from sympy.simplify.fu import TRpower
|
| 1490 |
+
>>> from sympy.abc import x
|
| 1491 |
+
>>> from sympy import cos, sin
|
| 1492 |
+
>>> TRpower(sin(x)**6)
|
| 1493 |
+
-15*cos(2*x)/32 + 3*cos(4*x)/16 - cos(6*x)/32 + 5/16
|
| 1494 |
+
>>> TRpower(sin(x)**3*cos(2*x)**4)
|
| 1495 |
+
(3*sin(x)/4 - sin(3*x)/4)*(cos(4*x)/2 + cos(8*x)/8 + 3/8)
|
| 1496 |
+
|
| 1497 |
+
References
|
| 1498 |
+
==========
|
| 1499 |
+
|
| 1500 |
+
.. [1] https://en.wikipedia.org/wiki/List_of_trigonometric_identities#Power-reduction_formulae
|
| 1501 |
+
|
| 1502 |
+
"""
|
| 1503 |
+
|
| 1504 |
+
def f(rv):
|
| 1505 |
+
if not (isinstance(rv, Pow) and isinstance(rv.base, (sin, cos))):
|
| 1506 |
+
return rv
|
| 1507 |
+
b, n = rv.as_base_exp()
|
| 1508 |
+
x = b.args[0]
|
| 1509 |
+
if n.is_Integer and n.is_positive:
|
| 1510 |
+
if n.is_odd and isinstance(b, cos):
|
| 1511 |
+
rv = 2**(1-n)*Add(*[binomial(n, k)*cos((n - 2*k)*x)
|
| 1512 |
+
for k in range((n + 1)/2)])
|
| 1513 |
+
elif n.is_odd and isinstance(b, sin):
|
| 1514 |
+
rv = 2**(1-n)*S.NegativeOne**((n-1)/2)*Add(*[binomial(n, k)*
|
| 1515 |
+
S.NegativeOne**k*sin((n - 2*k)*x) for k in range((n + 1)/2)])
|
| 1516 |
+
elif n.is_even and isinstance(b, cos):
|
| 1517 |
+
rv = 2**(1-n)*Add(*[binomial(n, k)*cos((n - 2*k)*x)
|
| 1518 |
+
for k in range(n/2)])
|
| 1519 |
+
elif n.is_even and isinstance(b, sin):
|
| 1520 |
+
rv = 2**(1-n)*S.NegativeOne**(n/2)*Add(*[binomial(n, k)*
|
| 1521 |
+
S.NegativeOne**k*cos((n - 2*k)*x) for k in range(n/2)])
|
| 1522 |
+
if n.is_even:
|
| 1523 |
+
rv += 2**(-n)*binomial(n, n/2)
|
| 1524 |
+
return rv
|
| 1525 |
+
|
| 1526 |
+
return bottom_up(rv, f)
|
| 1527 |
+
|
| 1528 |
+
|
| 1529 |
+
def L(rv):
|
| 1530 |
+
"""Return count of trigonometric functions in expression.
|
| 1531 |
+
|
| 1532 |
+
Examples
|
| 1533 |
+
========
|
| 1534 |
+
|
| 1535 |
+
>>> from sympy.simplify.fu import L
|
| 1536 |
+
>>> from sympy.abc import x
|
| 1537 |
+
>>> from sympy import cos, sin
|
| 1538 |
+
>>> L(cos(x)+sin(x))
|
| 1539 |
+
2
|
| 1540 |
+
"""
|
| 1541 |
+
return S(rv.count(TrigonometricFunction))
|
| 1542 |
+
|
| 1543 |
+
|
| 1544 |
+
# ============== end of basic Fu-like tools =====================
|
| 1545 |
+
|
| 1546 |
+
if SYMPY_DEBUG:
|
| 1547 |
+
(TR0, TR1, TR2, TR3, TR4, TR5, TR6, TR7, TR8, TR9, TR10, TR11, TR12, TR13,
|
| 1548 |
+
TR2i, TRmorrie, TR14, TR15, TR16, TR12i, TR111, TR22
|
| 1549 |
+
)= list(map(debug,
|
| 1550 |
+
(TR0, TR1, TR2, TR3, TR4, TR5, TR6, TR7, TR8, TR9, TR10, TR11, TR12, TR13,
|
| 1551 |
+
TR2i, TRmorrie, TR14, TR15, TR16, TR12i, TR111, TR22)))
|
| 1552 |
+
|
| 1553 |
+
|
| 1554 |
+
# tuples are chains -- (f, g) -> lambda x: g(f(x))
|
| 1555 |
+
# lists are choices -- [f, g] -> lambda x: min(f(x), g(x), key=objective)
|
| 1556 |
+
|
| 1557 |
+
CTR1 = [(TR5, TR0), (TR6, TR0), identity]
|
| 1558 |
+
|
| 1559 |
+
CTR2 = (TR11, [(TR5, TR0), (TR6, TR0), TR0])
|
| 1560 |
+
|
| 1561 |
+
CTR3 = [(TRmorrie, TR8, TR0), (TRmorrie, TR8, TR10i, TR0), identity]
|
| 1562 |
+
|
| 1563 |
+
CTR4 = [(TR4, TR10i), identity]
|
| 1564 |
+
|
| 1565 |
+
RL1 = (TR4, TR3, TR4, TR12, TR4, TR13, TR4, TR0)
|
| 1566 |
+
|
| 1567 |
+
|
| 1568 |
+
# XXX it's a little unclear how this one is to be implemented
|
| 1569 |
+
# see Fu paper of reference, page 7. What is the Union symbol referring to?
|
| 1570 |
+
# The diagram shows all these as one chain of transformations, but the
|
| 1571 |
+
# text refers to them being applied independently. Also, a break
|
| 1572 |
+
# if L starts to increase has not been implemented.
|
| 1573 |
+
RL2 = [
|
| 1574 |
+
(TR4, TR3, TR10, TR4, TR3, TR11),
|
| 1575 |
+
(TR5, TR7, TR11, TR4),
|
| 1576 |
+
(CTR3, CTR1, TR9, CTR2, TR4, TR9, TR9, CTR4),
|
| 1577 |
+
identity,
|
| 1578 |
+
]
|
| 1579 |
+
|
| 1580 |
+
|
| 1581 |
+
def fu(rv, measure=lambda x: (L(x), x.count_ops())):
|
| 1582 |
+
"""Attempt to simplify expression by using transformation rules given
|
| 1583 |
+
in the algorithm by Fu et al.
|
| 1584 |
+
|
| 1585 |
+
:func:`fu` will try to minimize the objective function ``measure``.
|
| 1586 |
+
By default this first minimizes the number of trig terms and then minimizes
|
| 1587 |
+
the number of total operations.
|
| 1588 |
+
|
| 1589 |
+
Examples
|
| 1590 |
+
========
|
| 1591 |
+
|
| 1592 |
+
>>> from sympy.simplify.fu import fu
|
| 1593 |
+
>>> from sympy import cos, sin, tan, pi, S, sqrt
|
| 1594 |
+
>>> from sympy.abc import x, y, a, b
|
| 1595 |
+
|
| 1596 |
+
>>> fu(sin(50)**2 + cos(50)**2 + sin(pi/6))
|
| 1597 |
+
3/2
|
| 1598 |
+
>>> fu(sqrt(6)*cos(x) + sqrt(2)*sin(x))
|
| 1599 |
+
2*sqrt(2)*sin(x + pi/3)
|
| 1600 |
+
|
| 1601 |
+
CTR1 example
|
| 1602 |
+
|
| 1603 |
+
>>> eq = sin(x)**4 - cos(y)**2 + sin(y)**2 + 2*cos(x)**2
|
| 1604 |
+
>>> fu(eq)
|
| 1605 |
+
cos(x)**4 - 2*cos(y)**2 + 2
|
| 1606 |
+
|
| 1607 |
+
CTR2 example
|
| 1608 |
+
|
| 1609 |
+
>>> fu(S.Half - cos(2*x)/2)
|
| 1610 |
+
sin(x)**2
|
| 1611 |
+
|
| 1612 |
+
CTR3 example
|
| 1613 |
+
|
| 1614 |
+
>>> fu(sin(a)*(cos(b) - sin(b)) + cos(a)*(sin(b) + cos(b)))
|
| 1615 |
+
sqrt(2)*sin(a + b + pi/4)
|
| 1616 |
+
|
| 1617 |
+
CTR4 example
|
| 1618 |
+
|
| 1619 |
+
>>> fu(sqrt(3)*cos(x)/2 + sin(x)/2)
|
| 1620 |
+
sin(x + pi/3)
|
| 1621 |
+
|
| 1622 |
+
Example 1
|
| 1623 |
+
|
| 1624 |
+
>>> fu(1-sin(2*x)**2/4-sin(y)**2-cos(x)**4)
|
| 1625 |
+
-cos(x)**2 + cos(y)**2
|
| 1626 |
+
|
| 1627 |
+
Example 2
|
| 1628 |
+
|
| 1629 |
+
>>> fu(cos(4*pi/9))
|
| 1630 |
+
sin(pi/18)
|
| 1631 |
+
>>> fu(cos(pi/9)*cos(2*pi/9)*cos(3*pi/9)*cos(4*pi/9))
|
| 1632 |
+
1/16
|
| 1633 |
+
|
| 1634 |
+
Example 3
|
| 1635 |
+
|
| 1636 |
+
>>> fu(tan(7*pi/18)+tan(5*pi/18)-sqrt(3)*tan(5*pi/18)*tan(7*pi/18))
|
| 1637 |
+
-sqrt(3)
|
| 1638 |
+
|
| 1639 |
+
Objective function example
|
| 1640 |
+
|
| 1641 |
+
>>> fu(sin(x)/cos(x)) # default objective function
|
| 1642 |
+
tan(x)
|
| 1643 |
+
>>> fu(sin(x)/cos(x), measure=lambda x: -x.count_ops()) # maximize op count
|
| 1644 |
+
sin(x)/cos(x)
|
| 1645 |
+
|
| 1646 |
+
References
|
| 1647 |
+
==========
|
| 1648 |
+
|
| 1649 |
+
.. [1] https://www.sciencedirect.com/science/article/pii/S0895717706001609
|
| 1650 |
+
"""
|
| 1651 |
+
fRL1 = greedy(RL1, measure)
|
| 1652 |
+
fRL2 = greedy(RL2, measure)
|
| 1653 |
+
|
| 1654 |
+
was = rv
|
| 1655 |
+
rv = sympify(rv)
|
| 1656 |
+
if not isinstance(rv, Expr):
|
| 1657 |
+
return rv.func(*[fu(a, measure=measure) for a in rv.args])
|
| 1658 |
+
rv = TR1(rv)
|
| 1659 |
+
if rv.has(tan, cot):
|
| 1660 |
+
rv1 = fRL1(rv)
|
| 1661 |
+
if (measure(rv1) < measure(rv)):
|
| 1662 |
+
rv = rv1
|
| 1663 |
+
if rv.has(tan, cot):
|
| 1664 |
+
rv = TR2(rv)
|
| 1665 |
+
if rv.has(sin, cos):
|
| 1666 |
+
rv1 = fRL2(rv)
|
| 1667 |
+
rv2 = TR8(TRmorrie(rv1))
|
| 1668 |
+
rv = min([was, rv, rv1, rv2], key=measure)
|
| 1669 |
+
return min(TR2i(rv), rv, key=measure)
|
| 1670 |
+
|
| 1671 |
+
|
| 1672 |
+
def process_common_addends(rv, do, key2=None, key1=True):
|
| 1673 |
+
"""Apply ``do`` to addends of ``rv`` that (if ``key1=True``) share at least
|
| 1674 |
+
a common absolute value of their coefficient and the value of ``key2`` when
|
| 1675 |
+
applied to the argument. If ``key1`` is False ``key2`` must be supplied and
|
| 1676 |
+
will be the only key applied.
|
| 1677 |
+
"""
|
| 1678 |
+
|
| 1679 |
+
# collect by absolute value of coefficient and key2
|
| 1680 |
+
absc = defaultdict(list)
|
| 1681 |
+
if key1:
|
| 1682 |
+
for a in rv.args:
|
| 1683 |
+
c, a = a.as_coeff_Mul()
|
| 1684 |
+
if c < 0:
|
| 1685 |
+
c = -c
|
| 1686 |
+
a = -a # put the sign on `a`
|
| 1687 |
+
absc[(c, key2(a) if key2 else 1)].append(a)
|
| 1688 |
+
elif key2:
|
| 1689 |
+
for a in rv.args:
|
| 1690 |
+
absc[(S.One, key2(a))].append(a)
|
| 1691 |
+
else:
|
| 1692 |
+
raise ValueError('must have at least one key')
|
| 1693 |
+
|
| 1694 |
+
args = []
|
| 1695 |
+
hit = False
|
| 1696 |
+
for k in absc:
|
| 1697 |
+
v = absc[k]
|
| 1698 |
+
c, _ = k
|
| 1699 |
+
if len(v) > 1:
|
| 1700 |
+
e = Add(*v, evaluate=False)
|
| 1701 |
+
new = do(e)
|
| 1702 |
+
if new != e:
|
| 1703 |
+
e = new
|
| 1704 |
+
hit = True
|
| 1705 |
+
args.append(c*e)
|
| 1706 |
+
else:
|
| 1707 |
+
args.append(c*v[0])
|
| 1708 |
+
if hit:
|
| 1709 |
+
rv = Add(*args)
|
| 1710 |
+
|
| 1711 |
+
return rv
|
| 1712 |
+
|
| 1713 |
+
|
| 1714 |
+
fufuncs = '''
|
| 1715 |
+
TR0 TR1 TR2 TR3 TR4 TR5 TR6 TR7 TR8 TR9 TR10 TR10i TR11
|
| 1716 |
+
TR12 TR13 L TR2i TRmorrie TR12i
|
| 1717 |
+
TR14 TR15 TR16 TR111 TR22'''.split()
|
| 1718 |
+
FU = dict(list(zip(fufuncs, list(map(locals().get, fufuncs)))))
|
| 1719 |
+
|
| 1720 |
+
|
| 1721 |
+
def _roots():
|
| 1722 |
+
global _ROOT2, _ROOT3, _invROOT3
|
| 1723 |
+
_ROOT2, _ROOT3 = sqrt(2), sqrt(3)
|
| 1724 |
+
_invROOT3 = 1/_ROOT3
|
| 1725 |
+
_ROOT2 = None
|
| 1726 |
+
|
| 1727 |
+
|
| 1728 |
+
def trig_split(a, b, two=False):
|
| 1729 |
+
"""Return the gcd, s1, s2, a1, a2, bool where
|
| 1730 |
+
|
| 1731 |
+
If two is False (default) then::
|
| 1732 |
+
a + b = gcd*(s1*f(a1) + s2*f(a2)) where f = cos if bool else sin
|
| 1733 |
+
else:
|
| 1734 |
+
if bool, a + b was +/- cos(a1)*cos(a2) +/- sin(a1)*sin(a2) and equals
|
| 1735 |
+
n1*gcd*cos(a - b) if n1 == n2 else
|
| 1736 |
+
n1*gcd*cos(a + b)
|
| 1737 |
+
else a + b was +/- cos(a1)*sin(a2) +/- sin(a1)*cos(a2) and equals
|
| 1738 |
+
n1*gcd*sin(a + b) if n1 = n2 else
|
| 1739 |
+
n1*gcd*sin(b - a)
|
| 1740 |
+
|
| 1741 |
+
Examples
|
| 1742 |
+
========
|
| 1743 |
+
|
| 1744 |
+
>>> from sympy.simplify.fu import trig_split
|
| 1745 |
+
>>> from sympy.abc import x, y, z
|
| 1746 |
+
>>> from sympy import cos, sin, sqrt
|
| 1747 |
+
|
| 1748 |
+
>>> trig_split(cos(x), cos(y))
|
| 1749 |
+
(1, 1, 1, x, y, True)
|
| 1750 |
+
>>> trig_split(2*cos(x), -2*cos(y))
|
| 1751 |
+
(2, 1, -1, x, y, True)
|
| 1752 |
+
>>> trig_split(cos(x)*sin(y), cos(y)*sin(y))
|
| 1753 |
+
(sin(y), 1, 1, x, y, True)
|
| 1754 |
+
|
| 1755 |
+
>>> trig_split(cos(x), -sqrt(3)*sin(x), two=True)
|
| 1756 |
+
(2, 1, -1, x, pi/6, False)
|
| 1757 |
+
>>> trig_split(cos(x), sin(x), two=True)
|
| 1758 |
+
(sqrt(2), 1, 1, x, pi/4, False)
|
| 1759 |
+
>>> trig_split(cos(x), -sin(x), two=True)
|
| 1760 |
+
(sqrt(2), 1, -1, x, pi/4, False)
|
| 1761 |
+
>>> trig_split(sqrt(2)*cos(x), -sqrt(6)*sin(x), two=True)
|
| 1762 |
+
(2*sqrt(2), 1, -1, x, pi/6, False)
|
| 1763 |
+
>>> trig_split(-sqrt(6)*cos(x), -sqrt(2)*sin(x), two=True)
|
| 1764 |
+
(-2*sqrt(2), 1, 1, x, pi/3, False)
|
| 1765 |
+
>>> trig_split(cos(x)/sqrt(6), sin(x)/sqrt(2), two=True)
|
| 1766 |
+
(sqrt(6)/3, 1, 1, x, pi/6, False)
|
| 1767 |
+
>>> trig_split(-sqrt(6)*cos(x)*sin(y), -sqrt(2)*sin(x)*sin(y), two=True)
|
| 1768 |
+
(-2*sqrt(2)*sin(y), 1, 1, x, pi/3, False)
|
| 1769 |
+
|
| 1770 |
+
>>> trig_split(cos(x), sin(x))
|
| 1771 |
+
>>> trig_split(cos(x), sin(z))
|
| 1772 |
+
>>> trig_split(2*cos(x), -sin(x))
|
| 1773 |
+
>>> trig_split(cos(x), -sqrt(3)*sin(x))
|
| 1774 |
+
>>> trig_split(cos(x)*cos(y), sin(x)*sin(z))
|
| 1775 |
+
>>> trig_split(cos(x)*cos(y), sin(x)*sin(y))
|
| 1776 |
+
>>> trig_split(-sqrt(6)*cos(x), sqrt(2)*sin(x)*sin(y), two=True)
|
| 1777 |
+
"""
|
| 1778 |
+
global _ROOT2, _ROOT3, _invROOT3
|
| 1779 |
+
if _ROOT2 is None:
|
| 1780 |
+
_roots()
|
| 1781 |
+
|
| 1782 |
+
a, b = [Factors(i) for i in (a, b)]
|
| 1783 |
+
ua, ub = a.normal(b)
|
| 1784 |
+
gcd = a.gcd(b).as_expr()
|
| 1785 |
+
n1 = n2 = 1
|
| 1786 |
+
if S.NegativeOne in ua.factors:
|
| 1787 |
+
ua = ua.quo(S.NegativeOne)
|
| 1788 |
+
n1 = -n1
|
| 1789 |
+
elif S.NegativeOne in ub.factors:
|
| 1790 |
+
ub = ub.quo(S.NegativeOne)
|
| 1791 |
+
n2 = -n2
|
| 1792 |
+
a, b = [i.as_expr() for i in (ua, ub)]
|
| 1793 |
+
|
| 1794 |
+
def pow_cos_sin(a, two):
|
| 1795 |
+
"""Return ``a`` as a tuple (r, c, s) such that
|
| 1796 |
+
``a = (r or 1)*(c or 1)*(s or 1)``.
|
| 1797 |
+
|
| 1798 |
+
Three arguments are returned (radical, c-factor, s-factor) as
|
| 1799 |
+
long as the conditions set by ``two`` are met; otherwise None is
|
| 1800 |
+
returned. If ``two`` is True there will be one or two non-None
|
| 1801 |
+
values in the tuple: c and s or c and r or s and r or s or c with c
|
| 1802 |
+
being a cosine function (if possible) else a sine, and s being a sine
|
| 1803 |
+
function (if possible) else oosine. If ``two`` is False then there
|
| 1804 |
+
will only be a c or s term in the tuple.
|
| 1805 |
+
|
| 1806 |
+
``two`` also require that either two cos and/or sin be present (with
|
| 1807 |
+
the condition that if the functions are the same the arguments are
|
| 1808 |
+
different or vice versa) or that a single cosine or a single sine
|
| 1809 |
+
be present with an optional radical.
|
| 1810 |
+
|
| 1811 |
+
If the above conditions dictated by ``two`` are not met then None
|
| 1812 |
+
is returned.
|
| 1813 |
+
"""
|
| 1814 |
+
c = s = None
|
| 1815 |
+
co = S.One
|
| 1816 |
+
if a.is_Mul:
|
| 1817 |
+
co, a = a.as_coeff_Mul()
|
| 1818 |
+
if len(a.args) > 2 or not two:
|
| 1819 |
+
return None
|
| 1820 |
+
if a.is_Mul:
|
| 1821 |
+
args = list(a.args)
|
| 1822 |
+
else:
|
| 1823 |
+
args = [a]
|
| 1824 |
+
a = args.pop(0)
|
| 1825 |
+
if isinstance(a, cos):
|
| 1826 |
+
c = a
|
| 1827 |
+
elif isinstance(a, sin):
|
| 1828 |
+
s = a
|
| 1829 |
+
elif a.is_Pow and a.exp is S.Half: # autoeval doesn't allow -1/2
|
| 1830 |
+
co *= a
|
| 1831 |
+
else:
|
| 1832 |
+
return None
|
| 1833 |
+
if args:
|
| 1834 |
+
b = args[0]
|
| 1835 |
+
if isinstance(b, cos):
|
| 1836 |
+
if c:
|
| 1837 |
+
s = b
|
| 1838 |
+
else:
|
| 1839 |
+
c = b
|
| 1840 |
+
elif isinstance(b, sin):
|
| 1841 |
+
if s:
|
| 1842 |
+
c = b
|
| 1843 |
+
else:
|
| 1844 |
+
s = b
|
| 1845 |
+
elif b.is_Pow and b.exp is S.Half:
|
| 1846 |
+
co *= b
|
| 1847 |
+
else:
|
| 1848 |
+
return None
|
| 1849 |
+
return co if co is not S.One else None, c, s
|
| 1850 |
+
elif isinstance(a, cos):
|
| 1851 |
+
c = a
|
| 1852 |
+
elif isinstance(a, sin):
|
| 1853 |
+
s = a
|
| 1854 |
+
if c is None and s is None:
|
| 1855 |
+
return
|
| 1856 |
+
co = co if co is not S.One else None
|
| 1857 |
+
return co, c, s
|
| 1858 |
+
|
| 1859 |
+
# get the parts
|
| 1860 |
+
m = pow_cos_sin(a, two)
|
| 1861 |
+
if m is None:
|
| 1862 |
+
return
|
| 1863 |
+
coa, ca, sa = m
|
| 1864 |
+
m = pow_cos_sin(b, two)
|
| 1865 |
+
if m is None:
|
| 1866 |
+
return
|
| 1867 |
+
cob, cb, sb = m
|
| 1868 |
+
|
| 1869 |
+
# check them
|
| 1870 |
+
if (not ca) and cb or ca and isinstance(ca, sin):
|
| 1871 |
+
coa, ca, sa, cob, cb, sb = cob, cb, sb, coa, ca, sa
|
| 1872 |
+
n1, n2 = n2, n1
|
| 1873 |
+
if not two: # need cos(x) and cos(y) or sin(x) and sin(y)
|
| 1874 |
+
c = ca or sa
|
| 1875 |
+
s = cb or sb
|
| 1876 |
+
if not isinstance(c, s.func):
|
| 1877 |
+
return None
|
| 1878 |
+
return gcd, n1, n2, c.args[0], s.args[0], isinstance(c, cos)
|
| 1879 |
+
else:
|
| 1880 |
+
if not coa and not cob:
|
| 1881 |
+
if (ca and cb and sa and sb):
|
| 1882 |
+
if isinstance(ca, sa.func) is not isinstance(cb, sb.func):
|
| 1883 |
+
return
|
| 1884 |
+
args = {j.args for j in (ca, sa)}
|
| 1885 |
+
if not all(i.args in args for i in (cb, sb)):
|
| 1886 |
+
return
|
| 1887 |
+
return gcd, n1, n2, ca.args[0], sa.args[0], isinstance(ca, sa.func)
|
| 1888 |
+
if ca and sa or cb and sb or \
|
| 1889 |
+
two and (ca is None and sa is None or cb is None and sb is None):
|
| 1890 |
+
return
|
| 1891 |
+
c = ca or sa
|
| 1892 |
+
s = cb or sb
|
| 1893 |
+
if c.args != s.args:
|
| 1894 |
+
return
|
| 1895 |
+
if not coa:
|
| 1896 |
+
coa = S.One
|
| 1897 |
+
if not cob:
|
| 1898 |
+
cob = S.One
|
| 1899 |
+
if coa is cob:
|
| 1900 |
+
gcd *= _ROOT2
|
| 1901 |
+
return gcd, n1, n2, c.args[0], pi/4, False
|
| 1902 |
+
elif coa/cob == _ROOT3:
|
| 1903 |
+
gcd *= 2*cob
|
| 1904 |
+
return gcd, n1, n2, c.args[0], pi/3, False
|
| 1905 |
+
elif coa/cob == _invROOT3:
|
| 1906 |
+
gcd *= 2*coa
|
| 1907 |
+
return gcd, n1, n2, c.args[0], pi/6, False
|
| 1908 |
+
|
| 1909 |
+
|
| 1910 |
+
def as_f_sign_1(e):
|
| 1911 |
+
"""If ``e`` is a sum that can be written as ``g*(a + s)`` where
|
| 1912 |
+
``s`` is ``+/-1``, return ``g``, ``a``, and ``s`` where ``a`` does
|
| 1913 |
+
not have a leading negative coefficient.
|
| 1914 |
+
|
| 1915 |
+
Examples
|
| 1916 |
+
========
|
| 1917 |
+
|
| 1918 |
+
>>> from sympy.simplify.fu import as_f_sign_1
|
| 1919 |
+
>>> from sympy.abc import x
|
| 1920 |
+
>>> as_f_sign_1(x + 1)
|
| 1921 |
+
(1, x, 1)
|
| 1922 |
+
>>> as_f_sign_1(x - 1)
|
| 1923 |
+
(1, x, -1)
|
| 1924 |
+
>>> as_f_sign_1(-x + 1)
|
| 1925 |
+
(-1, x, -1)
|
| 1926 |
+
>>> as_f_sign_1(-x - 1)
|
| 1927 |
+
(-1, x, 1)
|
| 1928 |
+
>>> as_f_sign_1(2*x + 2)
|
| 1929 |
+
(2, x, 1)
|
| 1930 |
+
"""
|
| 1931 |
+
if not e.is_Add or len(e.args) != 2:
|
| 1932 |
+
return
|
| 1933 |
+
# exact match
|
| 1934 |
+
a, b = e.args
|
| 1935 |
+
if a in (S.NegativeOne, S.One):
|
| 1936 |
+
g = S.One
|
| 1937 |
+
if b.is_Mul and b.args[0].is_Number and b.args[0] < 0:
|
| 1938 |
+
a, b = -a, -b
|
| 1939 |
+
g = -g
|
| 1940 |
+
return g, b, a
|
| 1941 |
+
# gcd match
|
| 1942 |
+
a, b = [Factors(i) for i in e.args]
|
| 1943 |
+
ua, ub = a.normal(b)
|
| 1944 |
+
gcd = a.gcd(b).as_expr()
|
| 1945 |
+
if S.NegativeOne in ua.factors:
|
| 1946 |
+
ua = ua.quo(S.NegativeOne)
|
| 1947 |
+
n1 = -1
|
| 1948 |
+
n2 = 1
|
| 1949 |
+
elif S.NegativeOne in ub.factors:
|
| 1950 |
+
ub = ub.quo(S.NegativeOne)
|
| 1951 |
+
n1 = 1
|
| 1952 |
+
n2 = -1
|
| 1953 |
+
else:
|
| 1954 |
+
n1 = n2 = 1
|
| 1955 |
+
a, b = [i.as_expr() for i in (ua, ub)]
|
| 1956 |
+
if a is S.One:
|
| 1957 |
+
a, b = b, a
|
| 1958 |
+
n1, n2 = n2, n1
|
| 1959 |
+
if n1 == -1:
|
| 1960 |
+
gcd = -gcd
|
| 1961 |
+
n2 = -n2
|
| 1962 |
+
|
| 1963 |
+
if b is S.One:
|
| 1964 |
+
return gcd, a, n2
|
| 1965 |
+
|
| 1966 |
+
|
| 1967 |
+
def _osborne(e, d):
|
| 1968 |
+
"""Replace all hyperbolic functions with trig functions using
|
| 1969 |
+
the Osborne rule.
|
| 1970 |
+
|
| 1971 |
+
Notes
|
| 1972 |
+
=====
|
| 1973 |
+
|
| 1974 |
+
``d`` is a dummy variable to prevent automatic evaluation
|
| 1975 |
+
of trigonometric/hyperbolic functions.
|
| 1976 |
+
|
| 1977 |
+
|
| 1978 |
+
References
|
| 1979 |
+
==========
|
| 1980 |
+
|
| 1981 |
+
.. [1] https://en.wikipedia.org/wiki/Hyperbolic_function
|
| 1982 |
+
"""
|
| 1983 |
+
|
| 1984 |
+
def f(rv):
|
| 1985 |
+
if not isinstance(rv, HyperbolicFunction):
|
| 1986 |
+
return rv
|
| 1987 |
+
a = rv.args[0]
|
| 1988 |
+
a = a*d if not a.is_Add else Add._from_args([i*d for i in a.args])
|
| 1989 |
+
if isinstance(rv, sinh):
|
| 1990 |
+
return I*sin(a)
|
| 1991 |
+
elif isinstance(rv, cosh):
|
| 1992 |
+
return cos(a)
|
| 1993 |
+
elif isinstance(rv, tanh):
|
| 1994 |
+
return I*tan(a)
|
| 1995 |
+
elif isinstance(rv, coth):
|
| 1996 |
+
return cot(a)/I
|
| 1997 |
+
elif isinstance(rv, sech):
|
| 1998 |
+
return sec(a)
|
| 1999 |
+
elif isinstance(rv, csch):
|
| 2000 |
+
return csc(a)/I
|
| 2001 |
+
else:
|
| 2002 |
+
raise NotImplementedError('unhandled %s' % rv.func)
|
| 2003 |
+
|
| 2004 |
+
return bottom_up(e, f)
|
| 2005 |
+
|
| 2006 |
+
|
| 2007 |
+
def _osbornei(e, d):
|
| 2008 |
+
"""Replace all trig functions with hyperbolic functions using
|
| 2009 |
+
the Osborne rule.
|
| 2010 |
+
|
| 2011 |
+
Notes
|
| 2012 |
+
=====
|
| 2013 |
+
|
| 2014 |
+
``d`` is a dummy variable to prevent automatic evaluation
|
| 2015 |
+
of trigonometric/hyperbolic functions.
|
| 2016 |
+
|
| 2017 |
+
References
|
| 2018 |
+
==========
|
| 2019 |
+
|
| 2020 |
+
.. [1] https://en.wikipedia.org/wiki/Hyperbolic_function
|
| 2021 |
+
"""
|
| 2022 |
+
|
| 2023 |
+
def f(rv):
|
| 2024 |
+
if not isinstance(rv, TrigonometricFunction):
|
| 2025 |
+
return rv
|
| 2026 |
+
const, x = rv.args[0].as_independent(d, as_Add=True)
|
| 2027 |
+
a = x.xreplace({d: S.One}) + const*I
|
| 2028 |
+
if isinstance(rv, sin):
|
| 2029 |
+
return sinh(a)/I
|
| 2030 |
+
elif isinstance(rv, cos):
|
| 2031 |
+
return cosh(a)
|
| 2032 |
+
elif isinstance(rv, tan):
|
| 2033 |
+
return tanh(a)/I
|
| 2034 |
+
elif isinstance(rv, cot):
|
| 2035 |
+
return coth(a)*I
|
| 2036 |
+
elif isinstance(rv, sec):
|
| 2037 |
+
return sech(a)
|
| 2038 |
+
elif isinstance(rv, csc):
|
| 2039 |
+
return csch(a)*I
|
| 2040 |
+
else:
|
| 2041 |
+
raise NotImplementedError('unhandled %s' % rv.func)
|
| 2042 |
+
|
| 2043 |
+
return bottom_up(e, f)
|
| 2044 |
+
|
| 2045 |
+
|
| 2046 |
+
def hyper_as_trig(rv):
|
| 2047 |
+
"""Return an expression containing hyperbolic functions in terms
|
| 2048 |
+
of trigonometric functions. Any trigonometric functions initially
|
| 2049 |
+
present are replaced with Dummy symbols and the function to undo
|
| 2050 |
+
the masking and the conversion back to hyperbolics is also returned. It
|
| 2051 |
+
should always be true that::
|
| 2052 |
+
|
| 2053 |
+
t, f = hyper_as_trig(expr)
|
| 2054 |
+
expr == f(t)
|
| 2055 |
+
|
| 2056 |
+
Examples
|
| 2057 |
+
========
|
| 2058 |
+
|
| 2059 |
+
>>> from sympy.simplify.fu import hyper_as_trig, fu
|
| 2060 |
+
>>> from sympy.abc import x
|
| 2061 |
+
>>> from sympy import cosh, sinh
|
| 2062 |
+
>>> eq = sinh(x)**2 + cosh(x)**2
|
| 2063 |
+
>>> t, f = hyper_as_trig(eq)
|
| 2064 |
+
>>> f(fu(t))
|
| 2065 |
+
cosh(2*x)
|
| 2066 |
+
|
| 2067 |
+
References
|
| 2068 |
+
==========
|
| 2069 |
+
|
| 2070 |
+
.. [1] https://en.wikipedia.org/wiki/Hyperbolic_function
|
| 2071 |
+
"""
|
| 2072 |
+
from sympy.simplify.simplify import signsimp
|
| 2073 |
+
from sympy.simplify.radsimp import collect
|
| 2074 |
+
|
| 2075 |
+
# mask off trig functions
|
| 2076 |
+
trigs = rv.atoms(TrigonometricFunction)
|
| 2077 |
+
reps = [(t, Dummy()) for t in trigs]
|
| 2078 |
+
masked = rv.xreplace(dict(reps))
|
| 2079 |
+
|
| 2080 |
+
# get inversion substitutions in place
|
| 2081 |
+
reps = [(v, k) for k, v in reps]
|
| 2082 |
+
|
| 2083 |
+
d = Dummy()
|
| 2084 |
+
|
| 2085 |
+
return _osborne(masked, d), lambda x: collect(signsimp(
|
| 2086 |
+
_osbornei(x, d).xreplace(dict(reps))), S.ImaginaryUnit)
|
| 2087 |
+
|
| 2088 |
+
|
| 2089 |
+
def sincos_to_sum(expr):
|
| 2090 |
+
"""Convert products and powers of sin and cos to sums.
|
| 2091 |
+
|
| 2092 |
+
Explanation
|
| 2093 |
+
===========
|
| 2094 |
+
|
| 2095 |
+
Applied power reduction TRpower first, then expands products, and
|
| 2096 |
+
converts products to sums with TR8.
|
| 2097 |
+
|
| 2098 |
+
Examples
|
| 2099 |
+
========
|
| 2100 |
+
|
| 2101 |
+
>>> from sympy.simplify.fu import sincos_to_sum
|
| 2102 |
+
>>> from sympy.abc import x
|
| 2103 |
+
>>> from sympy import cos, sin
|
| 2104 |
+
>>> sincos_to_sum(16*sin(x)**3*cos(2*x)**2)
|
| 2105 |
+
7*sin(x) - 5*sin(3*x) + 3*sin(5*x) - sin(7*x)
|
| 2106 |
+
"""
|
| 2107 |
+
|
| 2108 |
+
if not expr.has(cos, sin):
|
| 2109 |
+
return expr
|
| 2110 |
+
else:
|
| 2111 |
+
return TR8(expand_mul(TRpower(expr)))
|
lib/python3.12/site-packages/sympy/simplify/gammasimp.py
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core import Function, S, Mul, Pow, Add
|
| 2 |
+
from sympy.core.sorting import ordered, default_sort_key
|
| 3 |
+
from sympy.core.function import expand_func
|
| 4 |
+
from sympy.core.symbol import Dummy
|
| 5 |
+
from sympy.functions import gamma, sqrt, sin
|
| 6 |
+
from sympy.polys import factor, cancel
|
| 7 |
+
from sympy.utilities.iterables import sift, uniq
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def gammasimp(expr):
|
| 11 |
+
r"""
|
| 12 |
+
Simplify expressions with gamma functions.
|
| 13 |
+
|
| 14 |
+
Explanation
|
| 15 |
+
===========
|
| 16 |
+
|
| 17 |
+
This function takes as input an expression containing gamma
|
| 18 |
+
functions or functions that can be rewritten in terms of gamma
|
| 19 |
+
functions and tries to minimize the number of those functions and
|
| 20 |
+
reduce the size of their arguments.
|
| 21 |
+
|
| 22 |
+
The algorithm works by rewriting all gamma functions as expressions
|
| 23 |
+
involving rising factorials (Pochhammer symbols) and applies
|
| 24 |
+
recurrence relations and other transformations applicable to rising
|
| 25 |
+
factorials, to reduce their arguments, possibly letting the resulting
|
| 26 |
+
rising factorial to cancel. Rising factorials with the second argument
|
| 27 |
+
being an integer are expanded into polynomial forms and finally all
|
| 28 |
+
other rising factorial are rewritten in terms of gamma functions.
|
| 29 |
+
|
| 30 |
+
Then the following two steps are performed.
|
| 31 |
+
|
| 32 |
+
1. Reduce the number of gammas by applying the reflection theorem
|
| 33 |
+
gamma(x)*gamma(1-x) == pi/sin(pi*x).
|
| 34 |
+
2. Reduce the number of gammas by applying the multiplication theorem
|
| 35 |
+
gamma(x)*gamma(x+1/n)*...*gamma(x+(n-1)/n) == C*gamma(n*x).
|
| 36 |
+
|
| 37 |
+
It then reduces the number of prefactors by absorbing them into gammas
|
| 38 |
+
where possible and expands gammas with rational argument.
|
| 39 |
+
|
| 40 |
+
All transformation rules can be found (or were derived from) here:
|
| 41 |
+
|
| 42 |
+
.. [1] https://functions.wolfram.com/GammaBetaErf/Pochhammer/17/01/02/
|
| 43 |
+
.. [2] https://functions.wolfram.com/GammaBetaErf/Pochhammer/27/01/0005/
|
| 44 |
+
|
| 45 |
+
Examples
|
| 46 |
+
========
|
| 47 |
+
|
| 48 |
+
>>> from sympy.simplify import gammasimp
|
| 49 |
+
>>> from sympy import gamma, Symbol
|
| 50 |
+
>>> from sympy.abc import x
|
| 51 |
+
>>> n = Symbol('n', integer = True)
|
| 52 |
+
|
| 53 |
+
>>> gammasimp(gamma(x)/gamma(x - 3))
|
| 54 |
+
(x - 3)*(x - 2)*(x - 1)
|
| 55 |
+
>>> gammasimp(gamma(n + 3))
|
| 56 |
+
gamma(n + 3)
|
| 57 |
+
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
expr = expr.rewrite(gamma)
|
| 61 |
+
|
| 62 |
+
# compute_ST will be looking for Functions and we don't want
|
| 63 |
+
# it looking for non-gamma functions: issue 22606
|
| 64 |
+
# so we mask free, non-gamma functions
|
| 65 |
+
f = expr.atoms(Function)
|
| 66 |
+
# take out gammas
|
| 67 |
+
gammas = {i for i in f if isinstance(i, gamma)}
|
| 68 |
+
if not gammas:
|
| 69 |
+
return expr # avoid side effects like factoring
|
| 70 |
+
f -= gammas
|
| 71 |
+
# keep only those without bound symbols
|
| 72 |
+
f = f & expr.as_dummy().atoms(Function)
|
| 73 |
+
if f:
|
| 74 |
+
dum, fun, simp = zip(*[
|
| 75 |
+
(Dummy(), fi, fi.func(*[
|
| 76 |
+
_gammasimp(a, as_comb=False) for a in fi.args]))
|
| 77 |
+
for fi in ordered(f)])
|
| 78 |
+
d = expr.xreplace(dict(zip(fun, dum)))
|
| 79 |
+
return _gammasimp(d, as_comb=False).xreplace(dict(zip(dum, simp)))
|
| 80 |
+
|
| 81 |
+
return _gammasimp(expr, as_comb=False)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _gammasimp(expr, as_comb):
|
| 85 |
+
"""
|
| 86 |
+
Helper function for gammasimp and combsimp.
|
| 87 |
+
|
| 88 |
+
Explanation
|
| 89 |
+
===========
|
| 90 |
+
|
| 91 |
+
Simplifies expressions written in terms of gamma function. If
|
| 92 |
+
as_comb is True, it tries to preserve integer arguments. See
|
| 93 |
+
docstring of gammasimp for more information. This was part of
|
| 94 |
+
combsimp() in combsimp.py.
|
| 95 |
+
"""
|
| 96 |
+
expr = expr.replace(gamma,
|
| 97 |
+
lambda n: _rf(1, (n - 1).expand()))
|
| 98 |
+
|
| 99 |
+
if as_comb:
|
| 100 |
+
expr = expr.replace(_rf,
|
| 101 |
+
lambda a, b: gamma(b + 1))
|
| 102 |
+
else:
|
| 103 |
+
expr = expr.replace(_rf,
|
| 104 |
+
lambda a, b: gamma(a + b)/gamma(a))
|
| 105 |
+
|
| 106 |
+
def rule_gamma(expr, level=0):
|
| 107 |
+
""" Simplify products of gamma functions further. """
|
| 108 |
+
|
| 109 |
+
if expr.is_Atom:
|
| 110 |
+
return expr
|
| 111 |
+
|
| 112 |
+
def gamma_rat(x):
|
| 113 |
+
# helper to simplify ratios of gammas
|
| 114 |
+
was = x.count(gamma)
|
| 115 |
+
xx = x.replace(gamma, lambda n: _rf(1, (n - 1).expand()
|
| 116 |
+
).replace(_rf, lambda a, b: gamma(a + b)/gamma(a)))
|
| 117 |
+
if xx.count(gamma) < was:
|
| 118 |
+
x = xx
|
| 119 |
+
return x
|
| 120 |
+
|
| 121 |
+
def gamma_factor(x):
|
| 122 |
+
# return True if there is a gamma factor in shallow args
|
| 123 |
+
if isinstance(x, gamma):
|
| 124 |
+
return True
|
| 125 |
+
if x.is_Add or x.is_Mul:
|
| 126 |
+
return any(gamma_factor(xi) for xi in x.args)
|
| 127 |
+
if x.is_Pow and (x.exp.is_integer or x.base.is_positive):
|
| 128 |
+
return gamma_factor(x.base)
|
| 129 |
+
return False
|
| 130 |
+
|
| 131 |
+
# recursion step
|
| 132 |
+
if level == 0:
|
| 133 |
+
expr = expr.func(*[rule_gamma(x, level + 1) for x in expr.args])
|
| 134 |
+
level += 1
|
| 135 |
+
|
| 136 |
+
if not expr.is_Mul:
|
| 137 |
+
return expr
|
| 138 |
+
|
| 139 |
+
# non-commutative step
|
| 140 |
+
if level == 1:
|
| 141 |
+
args, nc = expr.args_cnc()
|
| 142 |
+
if not args:
|
| 143 |
+
return expr
|
| 144 |
+
if nc:
|
| 145 |
+
return rule_gamma(Mul._from_args(args), level + 1)*Mul._from_args(nc)
|
| 146 |
+
level += 1
|
| 147 |
+
|
| 148 |
+
# pure gamma handling, not factor absorption
|
| 149 |
+
if level == 2:
|
| 150 |
+
T, F = sift(expr.args, gamma_factor, binary=True)
|
| 151 |
+
gamma_ind = Mul(*F)
|
| 152 |
+
d = Mul(*T)
|
| 153 |
+
|
| 154 |
+
nd, dd = d.as_numer_denom()
|
| 155 |
+
for ipass in range(2):
|
| 156 |
+
args = list(ordered(Mul.make_args(nd)))
|
| 157 |
+
for i, ni in enumerate(args):
|
| 158 |
+
if ni.is_Add:
|
| 159 |
+
ni, dd = Add(*[
|
| 160 |
+
rule_gamma(gamma_rat(a/dd), level + 1) for a in ni.args]
|
| 161 |
+
).as_numer_denom()
|
| 162 |
+
args[i] = ni
|
| 163 |
+
if not dd.has(gamma):
|
| 164 |
+
break
|
| 165 |
+
nd = Mul(*args)
|
| 166 |
+
if ipass == 0 and not gamma_factor(nd):
|
| 167 |
+
break
|
| 168 |
+
nd, dd = dd, nd # now process in reversed order
|
| 169 |
+
expr = gamma_ind*nd/dd
|
| 170 |
+
if not (expr.is_Mul and (gamma_factor(dd) or gamma_factor(nd))):
|
| 171 |
+
return expr
|
| 172 |
+
level += 1
|
| 173 |
+
|
| 174 |
+
# iteration until constant
|
| 175 |
+
if level == 3:
|
| 176 |
+
while True:
|
| 177 |
+
was = expr
|
| 178 |
+
expr = rule_gamma(expr, 4)
|
| 179 |
+
if expr == was:
|
| 180 |
+
return expr
|
| 181 |
+
|
| 182 |
+
numer_gammas = []
|
| 183 |
+
denom_gammas = []
|
| 184 |
+
numer_others = []
|
| 185 |
+
denom_others = []
|
| 186 |
+
def explicate(p):
|
| 187 |
+
if p is S.One:
|
| 188 |
+
return None, []
|
| 189 |
+
b, e = p.as_base_exp()
|
| 190 |
+
if e.is_Integer:
|
| 191 |
+
if isinstance(b, gamma):
|
| 192 |
+
return True, [b.args[0]]*e
|
| 193 |
+
else:
|
| 194 |
+
return False, [b]*e
|
| 195 |
+
else:
|
| 196 |
+
return False, [p]
|
| 197 |
+
|
| 198 |
+
newargs = list(ordered(expr.args))
|
| 199 |
+
while newargs:
|
| 200 |
+
n, d = newargs.pop().as_numer_denom()
|
| 201 |
+
isg, l = explicate(n)
|
| 202 |
+
if isg:
|
| 203 |
+
numer_gammas.extend(l)
|
| 204 |
+
elif isg is False:
|
| 205 |
+
numer_others.extend(l)
|
| 206 |
+
isg, l = explicate(d)
|
| 207 |
+
if isg:
|
| 208 |
+
denom_gammas.extend(l)
|
| 209 |
+
elif isg is False:
|
| 210 |
+
denom_others.extend(l)
|
| 211 |
+
|
| 212 |
+
# =========== level 2 work: pure gamma manipulation =========
|
| 213 |
+
|
| 214 |
+
if not as_comb:
|
| 215 |
+
# Try to reduce the number of gamma factors by applying the
|
| 216 |
+
# reflection formula gamma(x)*gamma(1-x) = pi/sin(pi*x)
|
| 217 |
+
for gammas, numer, denom in [(
|
| 218 |
+
numer_gammas, numer_others, denom_others),
|
| 219 |
+
(denom_gammas, denom_others, numer_others)]:
|
| 220 |
+
new = []
|
| 221 |
+
while gammas:
|
| 222 |
+
g1 = gammas.pop()
|
| 223 |
+
if g1.is_integer:
|
| 224 |
+
new.append(g1)
|
| 225 |
+
continue
|
| 226 |
+
for i, g2 in enumerate(gammas):
|
| 227 |
+
n = g1 + g2 - 1
|
| 228 |
+
if not n.is_Integer:
|
| 229 |
+
continue
|
| 230 |
+
numer.append(S.Pi)
|
| 231 |
+
denom.append(sin(S.Pi*g1))
|
| 232 |
+
gammas.pop(i)
|
| 233 |
+
if n > 0:
|
| 234 |
+
for k in range(n):
|
| 235 |
+
numer.append(1 - g1 + k)
|
| 236 |
+
elif n < 0:
|
| 237 |
+
for k in range(-n):
|
| 238 |
+
denom.append(-g1 - k)
|
| 239 |
+
break
|
| 240 |
+
else:
|
| 241 |
+
new.append(g1)
|
| 242 |
+
# /!\ updating IN PLACE
|
| 243 |
+
gammas[:] = new
|
| 244 |
+
|
| 245 |
+
# Try to reduce the number of gammas by using the duplication
|
| 246 |
+
# theorem to cancel an upper and lower: gamma(2*s)/gamma(s) =
|
| 247 |
+
# 2**(2*s + 1)/(4*sqrt(pi))*gamma(s + 1/2). Although this could
|
| 248 |
+
# be done with higher argument ratios like gamma(3*x)/gamma(x),
|
| 249 |
+
# this would not reduce the number of gammas as in this case.
|
| 250 |
+
for ng, dg, no, do in [(numer_gammas, denom_gammas, numer_others,
|
| 251 |
+
denom_others),
|
| 252 |
+
(denom_gammas, numer_gammas, denom_others,
|
| 253 |
+
numer_others)]:
|
| 254 |
+
|
| 255 |
+
while True:
|
| 256 |
+
for x in ng:
|
| 257 |
+
for y in dg:
|
| 258 |
+
n = x - 2*y
|
| 259 |
+
if n.is_Integer:
|
| 260 |
+
break
|
| 261 |
+
else:
|
| 262 |
+
continue
|
| 263 |
+
break
|
| 264 |
+
else:
|
| 265 |
+
break
|
| 266 |
+
ng.remove(x)
|
| 267 |
+
dg.remove(y)
|
| 268 |
+
if n > 0:
|
| 269 |
+
for k in range(n):
|
| 270 |
+
no.append(2*y + k)
|
| 271 |
+
elif n < 0:
|
| 272 |
+
for k in range(-n):
|
| 273 |
+
do.append(2*y - 1 - k)
|
| 274 |
+
ng.append(y + S.Half)
|
| 275 |
+
no.append(2**(2*y - 1))
|
| 276 |
+
do.append(sqrt(S.Pi))
|
| 277 |
+
|
| 278 |
+
# Try to reduce the number of gamma factors by applying the
|
| 279 |
+
# multiplication theorem (used when n gammas with args differing
|
| 280 |
+
# by 1/n mod 1 are encountered).
|
| 281 |
+
#
|
| 282 |
+
# run of 2 with args differing by 1/2
|
| 283 |
+
#
|
| 284 |
+
# >>> gammasimp(gamma(x)*gamma(x+S.Half))
|
| 285 |
+
# 2*sqrt(2)*2**(-2*x - 1/2)*sqrt(pi)*gamma(2*x)
|
| 286 |
+
#
|
| 287 |
+
# run of 3 args differing by 1/3 (mod 1)
|
| 288 |
+
#
|
| 289 |
+
# >>> gammasimp(gamma(x)*gamma(x+S(1)/3)*gamma(x+S(2)/3))
|
| 290 |
+
# 6*3**(-3*x - 1/2)*pi*gamma(3*x)
|
| 291 |
+
# >>> gammasimp(gamma(x)*gamma(x+S(1)/3)*gamma(x+S(5)/3))
|
| 292 |
+
# 2*3**(-3*x - 1/2)*pi*(3*x + 2)*gamma(3*x)
|
| 293 |
+
#
|
| 294 |
+
def _run(coeffs):
|
| 295 |
+
# find runs in coeffs such that the difference in terms (mod 1)
|
| 296 |
+
# of t1, t2, ..., tn is 1/n
|
| 297 |
+
u = list(uniq(coeffs))
|
| 298 |
+
for i in range(len(u)):
|
| 299 |
+
dj = ([((u[j] - u[i]) % 1, j) for j in range(i + 1, len(u))])
|
| 300 |
+
for one, j in dj:
|
| 301 |
+
if one.p == 1 and one.q != 1:
|
| 302 |
+
n = one.q
|
| 303 |
+
got = [i]
|
| 304 |
+
get = list(range(1, n))
|
| 305 |
+
for d, j in dj:
|
| 306 |
+
m = n*d
|
| 307 |
+
if m.is_Integer and m in get:
|
| 308 |
+
get.remove(m)
|
| 309 |
+
got.append(j)
|
| 310 |
+
if not get:
|
| 311 |
+
break
|
| 312 |
+
else:
|
| 313 |
+
continue
|
| 314 |
+
for i, j in enumerate(got):
|
| 315 |
+
c = u[j]
|
| 316 |
+
coeffs.remove(c)
|
| 317 |
+
got[i] = c
|
| 318 |
+
return one.q, got[0], got[1:]
|
| 319 |
+
|
| 320 |
+
def _mult_thm(gammas, numer, denom):
|
| 321 |
+
# pull off and analyze the leading coefficient from each gamma arg
|
| 322 |
+
# looking for runs in those Rationals
|
| 323 |
+
|
| 324 |
+
# expr -> coeff + resid -> rats[resid] = coeff
|
| 325 |
+
rats = {}
|
| 326 |
+
for g in gammas:
|
| 327 |
+
c, resid = g.as_coeff_Add()
|
| 328 |
+
rats.setdefault(resid, []).append(c)
|
| 329 |
+
|
| 330 |
+
# look for runs in Rationals for each resid
|
| 331 |
+
keys = sorted(rats, key=default_sort_key)
|
| 332 |
+
for resid in keys:
|
| 333 |
+
coeffs = sorted(rats[resid])
|
| 334 |
+
new = []
|
| 335 |
+
while True:
|
| 336 |
+
run = _run(coeffs)
|
| 337 |
+
if run is None:
|
| 338 |
+
break
|
| 339 |
+
|
| 340 |
+
# process the sequence that was found:
|
| 341 |
+
# 1) convert all the gamma functions to have the right
|
| 342 |
+
# argument (could be off by an integer)
|
| 343 |
+
# 2) append the factors corresponding to the theorem
|
| 344 |
+
# 3) append the new gamma function
|
| 345 |
+
|
| 346 |
+
n, ui, other = run
|
| 347 |
+
|
| 348 |
+
# (1)
|
| 349 |
+
for u in other:
|
| 350 |
+
con = resid + u - 1
|
| 351 |
+
for k in range(int(u - ui)):
|
| 352 |
+
numer.append(con - k)
|
| 353 |
+
|
| 354 |
+
con = n*(resid + ui) # for (2) and (3)
|
| 355 |
+
|
| 356 |
+
# (2)
|
| 357 |
+
numer.append((2*S.Pi)**(S(n - 1)/2)*
|
| 358 |
+
n**(S.Half - con))
|
| 359 |
+
# (3)
|
| 360 |
+
new.append(con)
|
| 361 |
+
|
| 362 |
+
# restore resid to coeffs
|
| 363 |
+
rats[resid] = [resid + c for c in coeffs] + new
|
| 364 |
+
|
| 365 |
+
# rebuild the gamma arguments
|
| 366 |
+
g = []
|
| 367 |
+
for resid in keys:
|
| 368 |
+
g += rats[resid]
|
| 369 |
+
# /!\ updating IN PLACE
|
| 370 |
+
gammas[:] = g
|
| 371 |
+
|
| 372 |
+
for l, numer, denom in [(numer_gammas, numer_others, denom_others),
|
| 373 |
+
(denom_gammas, denom_others, numer_others)]:
|
| 374 |
+
_mult_thm(l, numer, denom)
|
| 375 |
+
|
| 376 |
+
# =========== level >= 2 work: factor absorption =========
|
| 377 |
+
|
| 378 |
+
if level >= 2:
|
| 379 |
+
# Try to absorb factors into the gammas: x*gamma(x) -> gamma(x + 1)
|
| 380 |
+
# and gamma(x)/(x - 1) -> gamma(x - 1)
|
| 381 |
+
# This code (in particular repeated calls to find_fuzzy) can be very
|
| 382 |
+
# slow.
|
| 383 |
+
def find_fuzzy(l, x):
|
| 384 |
+
if not l:
|
| 385 |
+
return
|
| 386 |
+
S1, T1 = compute_ST(x)
|
| 387 |
+
for y in l:
|
| 388 |
+
S2, T2 = inv[y]
|
| 389 |
+
if T1 != T2 or (not S1.intersection(S2) and
|
| 390 |
+
(S1 != set() or S2 != set())):
|
| 391 |
+
continue
|
| 392 |
+
# XXX we want some simplification (e.g. cancel or
|
| 393 |
+
# simplify) but no matter what it's slow.
|
| 394 |
+
a = len(cancel(x/y).free_symbols)
|
| 395 |
+
b = len(x.free_symbols)
|
| 396 |
+
c = len(y.free_symbols)
|
| 397 |
+
# TODO is there a better heuristic?
|
| 398 |
+
if a == 0 and (b > 0 or c > 0):
|
| 399 |
+
return y
|
| 400 |
+
|
| 401 |
+
# We thus try to avoid expensive calls by building the following
|
| 402 |
+
# "invariants": For every factor or gamma function argument
|
| 403 |
+
# - the set of free symbols S
|
| 404 |
+
# - the set of functional components T
|
| 405 |
+
# We will only try to absorb if T1==T2 and (S1 intersect S2 != emptyset
|
| 406 |
+
# or S1 == S2 == emptyset)
|
| 407 |
+
inv = {}
|
| 408 |
+
|
| 409 |
+
def compute_ST(expr):
|
| 410 |
+
if expr in inv:
|
| 411 |
+
return inv[expr]
|
| 412 |
+
return (expr.free_symbols, expr.atoms(Function).union(
|
| 413 |
+
{e.exp for e in expr.atoms(Pow)}))
|
| 414 |
+
|
| 415 |
+
def update_ST(expr):
|
| 416 |
+
inv[expr] = compute_ST(expr)
|
| 417 |
+
for expr in numer_gammas + denom_gammas + numer_others + denom_others:
|
| 418 |
+
update_ST(expr)
|
| 419 |
+
|
| 420 |
+
for gammas, numer, denom in [(
|
| 421 |
+
numer_gammas, numer_others, denom_others),
|
| 422 |
+
(denom_gammas, denom_others, numer_others)]:
|
| 423 |
+
new = []
|
| 424 |
+
while gammas:
|
| 425 |
+
g = gammas.pop()
|
| 426 |
+
cont = True
|
| 427 |
+
while cont:
|
| 428 |
+
cont = False
|
| 429 |
+
y = find_fuzzy(numer, g)
|
| 430 |
+
if y is not None:
|
| 431 |
+
numer.remove(y)
|
| 432 |
+
if y != g:
|
| 433 |
+
numer.append(y/g)
|
| 434 |
+
update_ST(y/g)
|
| 435 |
+
g += 1
|
| 436 |
+
cont = True
|
| 437 |
+
y = find_fuzzy(denom, g - 1)
|
| 438 |
+
if y is not None:
|
| 439 |
+
denom.remove(y)
|
| 440 |
+
if y != g - 1:
|
| 441 |
+
numer.append((g - 1)/y)
|
| 442 |
+
update_ST((g - 1)/y)
|
| 443 |
+
g -= 1
|
| 444 |
+
cont = True
|
| 445 |
+
new.append(g)
|
| 446 |
+
# /!\ updating IN PLACE
|
| 447 |
+
gammas[:] = new
|
| 448 |
+
|
| 449 |
+
# =========== rebuild expr ==================================
|
| 450 |
+
|
| 451 |
+
return Mul(*[gamma(g) for g in numer_gammas]) \
|
| 452 |
+
/ Mul(*[gamma(g) for g in denom_gammas]) \
|
| 453 |
+
* Mul(*numer_others) / Mul(*denom_others)
|
| 454 |
+
|
| 455 |
+
was = factor(expr)
|
| 456 |
+
# (for some reason we cannot use Basic.replace in this case)
|
| 457 |
+
expr = rule_gamma(was)
|
| 458 |
+
if expr != was:
|
| 459 |
+
expr = factor(expr)
|
| 460 |
+
|
| 461 |
+
expr = expr.replace(gamma,
|
| 462 |
+
lambda n: expand_func(gamma(n)) if n.is_Rational else gamma(n))
|
| 463 |
+
|
| 464 |
+
return expr
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
class _rf(Function):
|
| 468 |
+
@classmethod
|
| 469 |
+
def eval(cls, a, b):
|
| 470 |
+
if b.is_Integer:
|
| 471 |
+
if not b:
|
| 472 |
+
return S.One
|
| 473 |
+
|
| 474 |
+
n = int(b)
|
| 475 |
+
|
| 476 |
+
if n > 0:
|
| 477 |
+
return Mul(*[a + i for i in range(n)])
|
| 478 |
+
elif n < 0:
|
| 479 |
+
return 1/Mul(*[a - i for i in range(1, -n + 1)])
|
| 480 |
+
else:
|
| 481 |
+
if b.is_Add:
|
| 482 |
+
c, _b = b.as_coeff_Add()
|
| 483 |
+
|
| 484 |
+
if c.is_Integer:
|
| 485 |
+
if c > 0:
|
| 486 |
+
return _rf(a, _b)*_rf(a + _b, c)
|
| 487 |
+
elif c < 0:
|
| 488 |
+
return _rf(a, _b)/_rf(a + _b + c, -c)
|
| 489 |
+
|
| 490 |
+
if a.is_Add:
|
| 491 |
+
c, _a = a.as_coeff_Add()
|
| 492 |
+
|
| 493 |
+
if c.is_Integer:
|
| 494 |
+
if c > 0:
|
| 495 |
+
return _rf(_a, b)*_rf(_a + b, c)/_rf(_a, c)
|
| 496 |
+
elif c < 0:
|
| 497 |
+
return _rf(_a, b)*_rf(_a + c, -c)/_rf(_a + b + c, -c)
|
lib/python3.12/site-packages/sympy/simplify/ratsimp.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from itertools import combinations_with_replacement
|
| 2 |
+
from sympy.core import symbols, Add, Dummy
|
| 3 |
+
from sympy.core.numbers import Rational
|
| 4 |
+
from sympy.polys import cancel, ComputationFailed, parallel_poly_from_expr, reduced, Poly
|
| 5 |
+
from sympy.polys.monomials import Monomial, monomial_div
|
| 6 |
+
from sympy.polys.polyerrors import DomainError, PolificationFailed
|
| 7 |
+
from sympy.utilities.misc import debug, debugf
|
| 8 |
+
|
| 9 |
+
def ratsimp(expr):
|
| 10 |
+
"""
|
| 11 |
+
Put an expression over a common denominator, cancel and reduce.
|
| 12 |
+
|
| 13 |
+
Examples
|
| 14 |
+
========
|
| 15 |
+
|
| 16 |
+
>>> from sympy import ratsimp
|
| 17 |
+
>>> from sympy.abc import x, y
|
| 18 |
+
>>> ratsimp(1/x + 1/y)
|
| 19 |
+
(x + y)/(x*y)
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
f, g = cancel(expr).as_numer_denom()
|
| 23 |
+
try:
|
| 24 |
+
Q, r = reduced(f, [g], field=True, expand=False)
|
| 25 |
+
except ComputationFailed:
|
| 26 |
+
return f/g
|
| 27 |
+
|
| 28 |
+
return Add(*Q) + cancel(r/g)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def ratsimpmodprime(expr, G, *gens, quick=True, polynomial=False, **args):
|
| 32 |
+
"""
|
| 33 |
+
Simplifies a rational expression ``expr`` modulo the prime ideal
|
| 34 |
+
generated by ``G``. ``G`` should be a Groebner basis of the
|
| 35 |
+
ideal.
|
| 36 |
+
|
| 37 |
+
Examples
|
| 38 |
+
========
|
| 39 |
+
|
| 40 |
+
>>> from sympy.simplify.ratsimp import ratsimpmodprime
|
| 41 |
+
>>> from sympy.abc import x, y
|
| 42 |
+
>>> eq = (x + y**5 + y)/(x - y)
|
| 43 |
+
>>> ratsimpmodprime(eq, [x*y**5 - x - y], x, y, order='lex')
|
| 44 |
+
(-x**2 - x*y - x - y)/(-x**2 + x*y)
|
| 45 |
+
|
| 46 |
+
If ``polynomial`` is ``False``, the algorithm computes a rational
|
| 47 |
+
simplification which minimizes the sum of the total degrees of
|
| 48 |
+
the numerator and the denominator.
|
| 49 |
+
|
| 50 |
+
If ``polynomial`` is ``True``, this function just brings numerator and
|
| 51 |
+
denominator into a canonical form. This is much faster, but has
|
| 52 |
+
potentially worse results.
|
| 53 |
+
|
| 54 |
+
References
|
| 55 |
+
==========
|
| 56 |
+
|
| 57 |
+
.. [1] M. Monagan, R. Pearce, Rational Simplification Modulo a Polynomial
|
| 58 |
+
Ideal, https://dl.acm.org/doi/pdf/10.1145/1145768.1145809
|
| 59 |
+
(specifically, the second algorithm)
|
| 60 |
+
"""
|
| 61 |
+
from sympy.solvers.solvers import solve
|
| 62 |
+
|
| 63 |
+
debug('ratsimpmodprime', expr)
|
| 64 |
+
|
| 65 |
+
# usual preparation of polynomials:
|
| 66 |
+
|
| 67 |
+
num, denom = cancel(expr).as_numer_denom()
|
| 68 |
+
|
| 69 |
+
try:
|
| 70 |
+
polys, opt = parallel_poly_from_expr([num, denom] + G, *gens, **args)
|
| 71 |
+
except PolificationFailed:
|
| 72 |
+
return expr
|
| 73 |
+
|
| 74 |
+
domain = opt.domain
|
| 75 |
+
|
| 76 |
+
if domain.has_assoc_Field:
|
| 77 |
+
opt.domain = domain.get_field()
|
| 78 |
+
else:
|
| 79 |
+
raise DomainError(
|
| 80 |
+
"Cannot compute rational simplification over %s" % domain)
|
| 81 |
+
|
| 82 |
+
# compute only once
|
| 83 |
+
leading_monomials = [g.LM(opt.order) for g in polys[2:]]
|
| 84 |
+
tested = set()
|
| 85 |
+
|
| 86 |
+
def staircase(n):
|
| 87 |
+
"""
|
| 88 |
+
Compute all monomials with degree less than ``n`` that are
|
| 89 |
+
not divisible by any element of ``leading_monomials``.
|
| 90 |
+
"""
|
| 91 |
+
if n == 0:
|
| 92 |
+
return [1]
|
| 93 |
+
S = []
|
| 94 |
+
for mi in combinations_with_replacement(range(len(opt.gens)), n):
|
| 95 |
+
m = [0]*len(opt.gens)
|
| 96 |
+
for i in mi:
|
| 97 |
+
m[i] += 1
|
| 98 |
+
if all(monomial_div(m, lmg) is None for lmg in
|
| 99 |
+
leading_monomials):
|
| 100 |
+
S.append(m)
|
| 101 |
+
|
| 102 |
+
return [Monomial(s).as_expr(*opt.gens) for s in S] + staircase(n - 1)
|
| 103 |
+
|
| 104 |
+
def _ratsimpmodprime(a, b, allsol, N=0, D=0):
|
| 105 |
+
r"""
|
| 106 |
+
Computes a rational simplification of ``a/b`` which minimizes
|
| 107 |
+
the sum of the total degrees of the numerator and the denominator.
|
| 108 |
+
|
| 109 |
+
Explanation
|
| 110 |
+
===========
|
| 111 |
+
|
| 112 |
+
The algorithm proceeds by looking at ``a * d - b * c`` modulo
|
| 113 |
+
the ideal generated by ``G`` for some ``c`` and ``d`` with degree
|
| 114 |
+
less than ``a`` and ``b`` respectively.
|
| 115 |
+
The coefficients of ``c`` and ``d`` are indeterminates and thus
|
| 116 |
+
the coefficients of the normalform of ``a * d - b * c`` are
|
| 117 |
+
linear polynomials in these indeterminates.
|
| 118 |
+
If these linear polynomials, considered as system of
|
| 119 |
+
equations, have a nontrivial solution, then `\frac{a}{b}
|
| 120 |
+
\equiv \frac{c}{d}` modulo the ideal generated by ``G``. So,
|
| 121 |
+
by construction, the degree of ``c`` and ``d`` is less than
|
| 122 |
+
the degree of ``a`` and ``b``, so a simpler representation
|
| 123 |
+
has been found.
|
| 124 |
+
After a simpler representation has been found, the algorithm
|
| 125 |
+
tries to reduce the degree of the numerator and denominator
|
| 126 |
+
and returns the result afterwards.
|
| 127 |
+
|
| 128 |
+
As an extension, if quick=False, we look at all possible degrees such
|
| 129 |
+
that the total degree is less than *or equal to* the best current
|
| 130 |
+
solution. We retain a list of all solutions of minimal degree, and try
|
| 131 |
+
to find the best one at the end.
|
| 132 |
+
"""
|
| 133 |
+
c, d = a, b
|
| 134 |
+
steps = 0
|
| 135 |
+
|
| 136 |
+
maxdeg = a.total_degree() + b.total_degree()
|
| 137 |
+
if quick:
|
| 138 |
+
bound = maxdeg - 1
|
| 139 |
+
else:
|
| 140 |
+
bound = maxdeg
|
| 141 |
+
while N + D <= bound:
|
| 142 |
+
if (N, D) in tested:
|
| 143 |
+
break
|
| 144 |
+
tested.add((N, D))
|
| 145 |
+
|
| 146 |
+
M1 = staircase(N)
|
| 147 |
+
M2 = staircase(D)
|
| 148 |
+
debugf('%s / %s: %s, %s', (N, D, M1, M2))
|
| 149 |
+
|
| 150 |
+
Cs = symbols("c:%d" % len(M1), cls=Dummy)
|
| 151 |
+
Ds = symbols("d:%d" % len(M2), cls=Dummy)
|
| 152 |
+
ng = Cs + Ds
|
| 153 |
+
|
| 154 |
+
c_hat = Poly(
|
| 155 |
+
sum(Cs[i] * M1[i] for i in range(len(M1))), opt.gens + ng)
|
| 156 |
+
d_hat = Poly(
|
| 157 |
+
sum(Ds[i] * M2[i] for i in range(len(M2))), opt.gens + ng)
|
| 158 |
+
|
| 159 |
+
r = reduced(a * d_hat - b * c_hat, G, opt.gens + ng,
|
| 160 |
+
order=opt.order, polys=True)[1]
|
| 161 |
+
|
| 162 |
+
S = Poly(r, gens=opt.gens).coeffs()
|
| 163 |
+
sol = solve(S, Cs + Ds, particular=True, quick=True)
|
| 164 |
+
|
| 165 |
+
if sol and not all(s == 0 for s in sol.values()):
|
| 166 |
+
c = c_hat.subs(sol)
|
| 167 |
+
d = d_hat.subs(sol)
|
| 168 |
+
|
| 169 |
+
# The "free" variables occurring before as parameters
|
| 170 |
+
# might still be in the substituted c, d, so set them
|
| 171 |
+
# to the value chosen before:
|
| 172 |
+
c = c.subs(dict(list(zip(Cs + Ds, [1] * (len(Cs) + len(Ds))))))
|
| 173 |
+
d = d.subs(dict(list(zip(Cs + Ds, [1] * (len(Cs) + len(Ds))))))
|
| 174 |
+
|
| 175 |
+
c = Poly(c, opt.gens)
|
| 176 |
+
d = Poly(d, opt.gens)
|
| 177 |
+
if d == 0:
|
| 178 |
+
raise ValueError('Ideal not prime?')
|
| 179 |
+
|
| 180 |
+
allsol.append((c_hat, d_hat, S, Cs + Ds))
|
| 181 |
+
if N + D != maxdeg:
|
| 182 |
+
allsol = [allsol[-1]]
|
| 183 |
+
|
| 184 |
+
break
|
| 185 |
+
|
| 186 |
+
steps += 1
|
| 187 |
+
N += 1
|
| 188 |
+
D += 1
|
| 189 |
+
|
| 190 |
+
if steps > 0:
|
| 191 |
+
c, d, allsol = _ratsimpmodprime(c, d, allsol, N, D - steps)
|
| 192 |
+
c, d, allsol = _ratsimpmodprime(c, d, allsol, N - steps, D)
|
| 193 |
+
|
| 194 |
+
return c, d, allsol
|
| 195 |
+
|
| 196 |
+
# preprocessing. this improves performance a bit when deg(num)
|
| 197 |
+
# and deg(denom) are large:
|
| 198 |
+
num = reduced(num, G, opt.gens, order=opt.order)[1]
|
| 199 |
+
denom = reduced(denom, G, opt.gens, order=opt.order)[1]
|
| 200 |
+
|
| 201 |
+
if polynomial:
|
| 202 |
+
return (num/denom).cancel()
|
| 203 |
+
|
| 204 |
+
c, d, allsol = _ratsimpmodprime(
|
| 205 |
+
Poly(num, opt.gens, domain=opt.domain), Poly(denom, opt.gens, domain=opt.domain), [])
|
| 206 |
+
if not quick and allsol:
|
| 207 |
+
debugf('Looking for best minimal solution. Got: %s', len(allsol))
|
| 208 |
+
newsol = []
|
| 209 |
+
for c_hat, d_hat, S, ng in allsol:
|
| 210 |
+
sol = solve(S, ng, particular=True, quick=False)
|
| 211 |
+
# all values of sol should be numbers; if not, solve is broken
|
| 212 |
+
newsol.append((c_hat.subs(sol), d_hat.subs(sol)))
|
| 213 |
+
c, d = min(newsol, key=lambda x: len(x[0].terms()) + len(x[1].terms()))
|
| 214 |
+
|
| 215 |
+
if not domain.is_Field:
|
| 216 |
+
cn, c = c.clear_denoms(convert=True)
|
| 217 |
+
dn, d = d.clear_denoms(convert=True)
|
| 218 |
+
r = Rational(cn, dn)
|
| 219 |
+
else:
|
| 220 |
+
r = Rational(1)
|
| 221 |
+
|
| 222 |
+
return (c*r.q)/(d*r.p)
|
lib/python3.12/site-packages/sympy/simplify/simplify.py
ADDED
|
@@ -0,0 +1,2150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import defaultdict
|
| 2 |
+
|
| 3 |
+
from sympy.concrete.products import Product
|
| 4 |
+
from sympy.concrete.summations import Sum
|
| 5 |
+
from sympy.core import (Basic, S, Add, Mul, Pow, Symbol, sympify,
|
| 6 |
+
expand_func, Function, Dummy, Expr, factor_terms,
|
| 7 |
+
expand_power_exp, Eq)
|
| 8 |
+
from sympy.core.exprtools import factor_nc
|
| 9 |
+
from sympy.core.parameters import global_parameters
|
| 10 |
+
from sympy.core.function import (expand_log, count_ops, _mexpand,
|
| 11 |
+
nfloat, expand_mul, expand)
|
| 12 |
+
from sympy.core.numbers import Float, I, pi, Rational, equal_valued
|
| 13 |
+
from sympy.core.relational import Relational
|
| 14 |
+
from sympy.core.rules import Transform
|
| 15 |
+
from sympy.core.sorting import ordered
|
| 16 |
+
from sympy.core.sympify import _sympify
|
| 17 |
+
from sympy.core.traversal import bottom_up as _bottom_up, walk as _walk
|
| 18 |
+
from sympy.functions import gamma, exp, sqrt, log, exp_polar, re
|
| 19 |
+
from sympy.functions.combinatorial.factorials import CombinatorialFunction
|
| 20 |
+
from sympy.functions.elementary.complexes import unpolarify, Abs, sign
|
| 21 |
+
from sympy.functions.elementary.exponential import ExpBase
|
| 22 |
+
from sympy.functions.elementary.hyperbolic import HyperbolicFunction
|
| 23 |
+
from sympy.functions.elementary.integers import ceiling
|
| 24 |
+
from sympy.functions.elementary.piecewise import (Piecewise, piecewise_fold,
|
| 25 |
+
piecewise_simplify)
|
| 26 |
+
from sympy.functions.elementary.trigonometric import TrigonometricFunction
|
| 27 |
+
from sympy.functions.special.bessel import (BesselBase, besselj, besseli,
|
| 28 |
+
besselk, bessely, jn)
|
| 29 |
+
from sympy.functions.special.tensor_functions import KroneckerDelta
|
| 30 |
+
from sympy.integrals.integrals import Integral
|
| 31 |
+
from sympy.matrices.expressions import (MatrixExpr, MatAdd, MatMul,
|
| 32 |
+
MatPow, MatrixSymbol)
|
| 33 |
+
from sympy.polys import together, cancel, factor
|
| 34 |
+
from sympy.polys.numberfields.minpoly import _is_sum_surds, _minimal_polynomial_sq
|
| 35 |
+
from sympy.simplify.combsimp import combsimp
|
| 36 |
+
from sympy.simplify.cse_opts import sub_pre, sub_post
|
| 37 |
+
from sympy.simplify.hyperexpand import hyperexpand
|
| 38 |
+
from sympy.simplify.powsimp import powsimp
|
| 39 |
+
from sympy.simplify.radsimp import radsimp, fraction, collect_abs
|
| 40 |
+
from sympy.simplify.sqrtdenest import sqrtdenest
|
| 41 |
+
from sympy.simplify.trigsimp import trigsimp, exptrigsimp
|
| 42 |
+
from sympy.utilities.decorator import deprecated
|
| 43 |
+
from sympy.utilities.iterables import has_variety, sift, subsets, iterable
|
| 44 |
+
from sympy.utilities.misc import as_int
|
| 45 |
+
|
| 46 |
+
import mpmath
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def separatevars(expr, symbols=[], dict=False, force=False):
|
| 50 |
+
"""
|
| 51 |
+
Separates variables in an expression, if possible. By
|
| 52 |
+
default, it separates with respect to all symbols in an
|
| 53 |
+
expression and collects constant coefficients that are
|
| 54 |
+
independent of symbols.
|
| 55 |
+
|
| 56 |
+
Explanation
|
| 57 |
+
===========
|
| 58 |
+
|
| 59 |
+
If ``dict=True`` then the separated terms will be returned
|
| 60 |
+
in a dictionary keyed to their corresponding symbols.
|
| 61 |
+
By default, all symbols in the expression will appear as
|
| 62 |
+
keys; if symbols are provided, then all those symbols will
|
| 63 |
+
be used as keys, and any terms in the expression containing
|
| 64 |
+
other symbols or non-symbols will be returned keyed to the
|
| 65 |
+
string 'coeff'. (Passing None for symbols will return the
|
| 66 |
+
expression in a dictionary keyed to 'coeff'.)
|
| 67 |
+
|
| 68 |
+
If ``force=True``, then bases of powers will be separated regardless
|
| 69 |
+
of assumptions on the symbols involved.
|
| 70 |
+
|
| 71 |
+
Notes
|
| 72 |
+
=====
|
| 73 |
+
|
| 74 |
+
The order of the factors is determined by Mul, so that the
|
| 75 |
+
separated expressions may not necessarily be grouped together.
|
| 76 |
+
|
| 77 |
+
Although factoring is necessary to separate variables in some
|
| 78 |
+
expressions, it is not necessary in all cases, so one should not
|
| 79 |
+
count on the returned factors being factored.
|
| 80 |
+
|
| 81 |
+
Examples
|
| 82 |
+
========
|
| 83 |
+
|
| 84 |
+
>>> from sympy.abc import x, y, z, alpha
|
| 85 |
+
>>> from sympy import separatevars, sin
|
| 86 |
+
>>> separatevars((x*y)**y)
|
| 87 |
+
(x*y)**y
|
| 88 |
+
>>> separatevars((x*y)**y, force=True)
|
| 89 |
+
x**y*y**y
|
| 90 |
+
|
| 91 |
+
>>> e = 2*x**2*z*sin(y)+2*z*x**2
|
| 92 |
+
>>> separatevars(e)
|
| 93 |
+
2*x**2*z*(sin(y) + 1)
|
| 94 |
+
>>> separatevars(e, symbols=(x, y), dict=True)
|
| 95 |
+
{'coeff': 2*z, x: x**2, y: sin(y) + 1}
|
| 96 |
+
>>> separatevars(e, [x, y, alpha], dict=True)
|
| 97 |
+
{'coeff': 2*z, alpha: 1, x: x**2, y: sin(y) + 1}
|
| 98 |
+
|
| 99 |
+
If the expression is not really separable, or is only partially
|
| 100 |
+
separable, separatevars will do the best it can to separate it
|
| 101 |
+
by using factoring.
|
| 102 |
+
|
| 103 |
+
>>> separatevars(x + x*y - 3*x**2)
|
| 104 |
+
-x*(3*x - y - 1)
|
| 105 |
+
|
| 106 |
+
If the expression is not separable then expr is returned unchanged
|
| 107 |
+
or (if dict=True) then None is returned.
|
| 108 |
+
|
| 109 |
+
>>> eq = 2*x + y*sin(x)
|
| 110 |
+
>>> separatevars(eq) == eq
|
| 111 |
+
True
|
| 112 |
+
>>> separatevars(2*x + y*sin(x), symbols=(x, y), dict=True) is None
|
| 113 |
+
True
|
| 114 |
+
|
| 115 |
+
"""
|
| 116 |
+
expr = sympify(expr)
|
| 117 |
+
if dict:
|
| 118 |
+
return _separatevars_dict(_separatevars(expr, force), symbols)
|
| 119 |
+
else:
|
| 120 |
+
return _separatevars(expr, force)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def _separatevars(expr, force):
|
| 124 |
+
if isinstance(expr, Abs):
|
| 125 |
+
arg = expr.args[0]
|
| 126 |
+
if arg.is_Mul and not arg.is_number:
|
| 127 |
+
s = separatevars(arg, dict=True, force=force)
|
| 128 |
+
if s is not None:
|
| 129 |
+
return Mul(*map(expr.func, s.values()))
|
| 130 |
+
else:
|
| 131 |
+
return expr
|
| 132 |
+
|
| 133 |
+
if len(expr.free_symbols) < 2:
|
| 134 |
+
return expr
|
| 135 |
+
|
| 136 |
+
# don't destroy a Mul since much of the work may already be done
|
| 137 |
+
if expr.is_Mul:
|
| 138 |
+
args = list(expr.args)
|
| 139 |
+
changed = False
|
| 140 |
+
for i, a in enumerate(args):
|
| 141 |
+
args[i] = separatevars(a, force)
|
| 142 |
+
changed = changed or args[i] != a
|
| 143 |
+
if changed:
|
| 144 |
+
expr = expr.func(*args)
|
| 145 |
+
return expr
|
| 146 |
+
|
| 147 |
+
# get a Pow ready for expansion
|
| 148 |
+
if expr.is_Pow and expr.base != S.Exp1:
|
| 149 |
+
expr = Pow(separatevars(expr.base, force=force), expr.exp)
|
| 150 |
+
|
| 151 |
+
# First try other expansion methods
|
| 152 |
+
expr = expr.expand(mul=False, multinomial=False, force=force)
|
| 153 |
+
|
| 154 |
+
_expr, reps = posify(expr) if force else (expr, {})
|
| 155 |
+
expr = factor(_expr).subs(reps)
|
| 156 |
+
|
| 157 |
+
if not expr.is_Add:
|
| 158 |
+
return expr
|
| 159 |
+
|
| 160 |
+
# Find any common coefficients to pull out
|
| 161 |
+
args = list(expr.args)
|
| 162 |
+
commonc = args[0].args_cnc(cset=True, warn=False)[0]
|
| 163 |
+
for i in args[1:]:
|
| 164 |
+
commonc &= i.args_cnc(cset=True, warn=False)[0]
|
| 165 |
+
commonc = Mul(*commonc)
|
| 166 |
+
commonc = commonc.as_coeff_Mul()[1] # ignore constants
|
| 167 |
+
commonc_set = commonc.args_cnc(cset=True, warn=False)[0]
|
| 168 |
+
|
| 169 |
+
# remove them
|
| 170 |
+
for i, a in enumerate(args):
|
| 171 |
+
c, nc = a.args_cnc(cset=True, warn=False)
|
| 172 |
+
c = c - commonc_set
|
| 173 |
+
args[i] = Mul(*c)*Mul(*nc)
|
| 174 |
+
nonsepar = Add(*args)
|
| 175 |
+
|
| 176 |
+
if len(nonsepar.free_symbols) > 1:
|
| 177 |
+
_expr = nonsepar
|
| 178 |
+
_expr, reps = posify(_expr) if force else (_expr, {})
|
| 179 |
+
_expr = (factor(_expr)).subs(reps)
|
| 180 |
+
|
| 181 |
+
if not _expr.is_Add:
|
| 182 |
+
nonsepar = _expr
|
| 183 |
+
|
| 184 |
+
return commonc*nonsepar
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def _separatevars_dict(expr, symbols):
|
| 188 |
+
if symbols:
|
| 189 |
+
if not all(t.is_Atom for t in symbols):
|
| 190 |
+
raise ValueError("symbols must be Atoms.")
|
| 191 |
+
symbols = list(symbols)
|
| 192 |
+
elif symbols is None:
|
| 193 |
+
return {'coeff': expr}
|
| 194 |
+
else:
|
| 195 |
+
symbols = list(expr.free_symbols)
|
| 196 |
+
if not symbols:
|
| 197 |
+
return None
|
| 198 |
+
|
| 199 |
+
ret = {i: [] for i in symbols + ['coeff']}
|
| 200 |
+
|
| 201 |
+
for i in Mul.make_args(expr):
|
| 202 |
+
expsym = i.free_symbols
|
| 203 |
+
intersection = set(symbols).intersection(expsym)
|
| 204 |
+
if len(intersection) > 1:
|
| 205 |
+
return None
|
| 206 |
+
if len(intersection) == 0:
|
| 207 |
+
# There are no symbols, so it is part of the coefficient
|
| 208 |
+
ret['coeff'].append(i)
|
| 209 |
+
else:
|
| 210 |
+
ret[intersection.pop()].append(i)
|
| 211 |
+
|
| 212 |
+
# rebuild
|
| 213 |
+
for k, v in ret.items():
|
| 214 |
+
ret[k] = Mul(*v)
|
| 215 |
+
|
| 216 |
+
return ret
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def posify(eq):
|
| 220 |
+
"""Return ``eq`` (with generic symbols made positive) and a
|
| 221 |
+
dictionary containing the mapping between the old and new
|
| 222 |
+
symbols.
|
| 223 |
+
|
| 224 |
+
Explanation
|
| 225 |
+
===========
|
| 226 |
+
|
| 227 |
+
Any symbol that has positive=None will be replaced with a positive dummy
|
| 228 |
+
symbol having the same name. This replacement will allow more symbolic
|
| 229 |
+
processing of expressions, especially those involving powers and
|
| 230 |
+
logarithms.
|
| 231 |
+
|
| 232 |
+
A dictionary that can be sent to subs to restore ``eq`` to its original
|
| 233 |
+
symbols is also returned.
|
| 234 |
+
|
| 235 |
+
>>> from sympy import posify, Symbol, log, solve
|
| 236 |
+
>>> from sympy.abc import x
|
| 237 |
+
>>> posify(x + Symbol('p', positive=True) + Symbol('n', negative=True))
|
| 238 |
+
(_x + n + p, {_x: x})
|
| 239 |
+
|
| 240 |
+
>>> eq = 1/x
|
| 241 |
+
>>> log(eq).expand()
|
| 242 |
+
log(1/x)
|
| 243 |
+
>>> log(posify(eq)[0]).expand()
|
| 244 |
+
-log(_x)
|
| 245 |
+
>>> p, rep = posify(eq)
|
| 246 |
+
>>> log(p).expand().subs(rep)
|
| 247 |
+
-log(x)
|
| 248 |
+
|
| 249 |
+
It is possible to apply the same transformations to an iterable
|
| 250 |
+
of expressions:
|
| 251 |
+
|
| 252 |
+
>>> eq = x**2 - 4
|
| 253 |
+
>>> solve(eq, x)
|
| 254 |
+
[-2, 2]
|
| 255 |
+
>>> eq_x, reps = posify([eq, x]); eq_x
|
| 256 |
+
[_x**2 - 4, _x]
|
| 257 |
+
>>> solve(*eq_x)
|
| 258 |
+
[2]
|
| 259 |
+
"""
|
| 260 |
+
eq = sympify(eq)
|
| 261 |
+
if iterable(eq):
|
| 262 |
+
f = type(eq)
|
| 263 |
+
eq = list(eq)
|
| 264 |
+
syms = set()
|
| 265 |
+
for e in eq:
|
| 266 |
+
syms = syms.union(e.atoms(Symbol))
|
| 267 |
+
reps = {}
|
| 268 |
+
for s in syms:
|
| 269 |
+
reps.update({v: k for k, v in posify(s)[1].items()})
|
| 270 |
+
for i, e in enumerate(eq):
|
| 271 |
+
eq[i] = e.subs(reps)
|
| 272 |
+
return f(eq), {r: s for s, r in reps.items()}
|
| 273 |
+
|
| 274 |
+
reps = {s: Dummy(s.name, positive=True, **s.assumptions0)
|
| 275 |
+
for s in eq.free_symbols if s.is_positive is None}
|
| 276 |
+
eq = eq.subs(reps)
|
| 277 |
+
return eq, {r: s for s, r in reps.items()}
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def hypersimp(f, k):
|
| 281 |
+
"""Given combinatorial term f(k) simplify its consecutive term ratio
|
| 282 |
+
i.e. f(k+1)/f(k). The input term can be composed of functions and
|
| 283 |
+
integer sequences which have equivalent representation in terms
|
| 284 |
+
of gamma special function.
|
| 285 |
+
|
| 286 |
+
Explanation
|
| 287 |
+
===========
|
| 288 |
+
|
| 289 |
+
The algorithm performs three basic steps:
|
| 290 |
+
|
| 291 |
+
1. Rewrite all functions in terms of gamma, if possible.
|
| 292 |
+
|
| 293 |
+
2. Rewrite all occurrences of gamma in terms of products
|
| 294 |
+
of gamma and rising factorial with integer, absolute
|
| 295 |
+
constant exponent.
|
| 296 |
+
|
| 297 |
+
3. Perform simplification of nested fractions, powers
|
| 298 |
+
and if the resulting expression is a quotient of
|
| 299 |
+
polynomials, reduce their total degree.
|
| 300 |
+
|
| 301 |
+
If f(k) is hypergeometric then as result we arrive with a
|
| 302 |
+
quotient of polynomials of minimal degree. Otherwise None
|
| 303 |
+
is returned.
|
| 304 |
+
|
| 305 |
+
For more information on the implemented algorithm refer to:
|
| 306 |
+
|
| 307 |
+
1. W. Koepf, Algorithms for m-fold Hypergeometric Summation,
|
| 308 |
+
Journal of Symbolic Computation (1995) 20, 399-417
|
| 309 |
+
"""
|
| 310 |
+
f = sympify(f)
|
| 311 |
+
|
| 312 |
+
g = f.subs(k, k + 1) / f
|
| 313 |
+
|
| 314 |
+
g = g.rewrite(gamma)
|
| 315 |
+
if g.has(Piecewise):
|
| 316 |
+
g = piecewise_fold(g)
|
| 317 |
+
g = g.args[-1][0]
|
| 318 |
+
g = expand_func(g)
|
| 319 |
+
g = powsimp(g, deep=True, combine='exp')
|
| 320 |
+
|
| 321 |
+
if g.is_rational_function(k):
|
| 322 |
+
return simplify(g, ratio=S.Infinity)
|
| 323 |
+
else:
|
| 324 |
+
return None
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def hypersimilar(f, g, k):
|
| 328 |
+
"""
|
| 329 |
+
Returns True if ``f`` and ``g`` are hyper-similar.
|
| 330 |
+
|
| 331 |
+
Explanation
|
| 332 |
+
===========
|
| 333 |
+
|
| 334 |
+
Similarity in hypergeometric sense means that a quotient of
|
| 335 |
+
f(k) and g(k) is a rational function in ``k``. This procedure
|
| 336 |
+
is useful in solving recurrence relations.
|
| 337 |
+
|
| 338 |
+
For more information see hypersimp().
|
| 339 |
+
|
| 340 |
+
"""
|
| 341 |
+
f, g = list(map(sympify, (f, g)))
|
| 342 |
+
|
| 343 |
+
h = (f/g).rewrite(gamma)
|
| 344 |
+
h = h.expand(func=True, basic=False)
|
| 345 |
+
|
| 346 |
+
return h.is_rational_function(k)
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def signsimp(expr, evaluate=None):
|
| 350 |
+
"""Make all Add sub-expressions canonical wrt sign.
|
| 351 |
+
|
| 352 |
+
Explanation
|
| 353 |
+
===========
|
| 354 |
+
|
| 355 |
+
If an Add subexpression, ``a``, can have a sign extracted,
|
| 356 |
+
as determined by could_extract_minus_sign, it is replaced
|
| 357 |
+
with Mul(-1, a, evaluate=False). This allows signs to be
|
| 358 |
+
extracted from powers and products.
|
| 359 |
+
|
| 360 |
+
Examples
|
| 361 |
+
========
|
| 362 |
+
|
| 363 |
+
>>> from sympy import signsimp, exp, symbols
|
| 364 |
+
>>> from sympy.abc import x, y
|
| 365 |
+
>>> i = symbols('i', odd=True)
|
| 366 |
+
>>> n = -1 + 1/x
|
| 367 |
+
>>> n/x/(-n)**2 - 1/n/x
|
| 368 |
+
(-1 + 1/x)/(x*(1 - 1/x)**2) - 1/(x*(-1 + 1/x))
|
| 369 |
+
>>> signsimp(_)
|
| 370 |
+
0
|
| 371 |
+
>>> x*n + x*-n
|
| 372 |
+
x*(-1 + 1/x) + x*(1 - 1/x)
|
| 373 |
+
>>> signsimp(_)
|
| 374 |
+
0
|
| 375 |
+
|
| 376 |
+
Since powers automatically handle leading signs
|
| 377 |
+
|
| 378 |
+
>>> (-2)**i
|
| 379 |
+
-2**i
|
| 380 |
+
|
| 381 |
+
signsimp can be used to put the base of a power with an integer
|
| 382 |
+
exponent into canonical form:
|
| 383 |
+
|
| 384 |
+
>>> n**i
|
| 385 |
+
(-1 + 1/x)**i
|
| 386 |
+
|
| 387 |
+
By default, signsimp does not leave behind any hollow simplification:
|
| 388 |
+
if making an Add canonical wrt sign didn't change the expression, the
|
| 389 |
+
original Add is restored. If this is not desired then the keyword
|
| 390 |
+
``evaluate`` can be set to False:
|
| 391 |
+
|
| 392 |
+
>>> e = exp(y - x)
|
| 393 |
+
>>> signsimp(e) == e
|
| 394 |
+
True
|
| 395 |
+
>>> signsimp(e, evaluate=False)
|
| 396 |
+
exp(-(x - y))
|
| 397 |
+
|
| 398 |
+
"""
|
| 399 |
+
if evaluate is None:
|
| 400 |
+
evaluate = global_parameters.evaluate
|
| 401 |
+
expr = sympify(expr)
|
| 402 |
+
if not isinstance(expr, (Expr, Relational)) or expr.is_Atom:
|
| 403 |
+
return expr
|
| 404 |
+
# get rid of an pre-existing unevaluation regarding sign
|
| 405 |
+
e = expr.replace(lambda x: x.is_Mul and -(-x) != x, lambda x: -(-x))
|
| 406 |
+
e = sub_post(sub_pre(e))
|
| 407 |
+
if not isinstance(e, (Expr, Relational)) or e.is_Atom:
|
| 408 |
+
return e
|
| 409 |
+
if e.is_Add:
|
| 410 |
+
rv = e.func(*[signsimp(a) for a in e.args])
|
| 411 |
+
if not evaluate and isinstance(rv, Add
|
| 412 |
+
) and rv.could_extract_minus_sign():
|
| 413 |
+
return Mul(S.NegativeOne, -rv, evaluate=False)
|
| 414 |
+
return rv
|
| 415 |
+
if evaluate:
|
| 416 |
+
e = e.replace(lambda x: x.is_Mul and -(-x) != x, lambda x: -(-x))
|
| 417 |
+
return e
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
def simplify(expr, ratio=1.7, measure=count_ops, rational=False, inverse=False, doit=True, **kwargs):
|
| 421 |
+
"""Simplifies the given expression.
|
| 422 |
+
|
| 423 |
+
Explanation
|
| 424 |
+
===========
|
| 425 |
+
|
| 426 |
+
Simplification is not a well defined term and the exact strategies
|
| 427 |
+
this function tries can change in the future versions of SymPy. If
|
| 428 |
+
your algorithm relies on "simplification" (whatever it is), try to
|
| 429 |
+
determine what you need exactly - is it powsimp()?, radsimp()?,
|
| 430 |
+
together()?, logcombine()?, or something else? And use this particular
|
| 431 |
+
function directly, because those are well defined and thus your algorithm
|
| 432 |
+
will be robust.
|
| 433 |
+
|
| 434 |
+
Nonetheless, especially for interactive use, or when you do not know
|
| 435 |
+
anything about the structure of the expression, simplify() tries to apply
|
| 436 |
+
intelligent heuristics to make the input expression "simpler". For
|
| 437 |
+
example:
|
| 438 |
+
|
| 439 |
+
>>> from sympy import simplify, cos, sin
|
| 440 |
+
>>> from sympy.abc import x, y
|
| 441 |
+
>>> a = (x + x**2)/(x*sin(y)**2 + x*cos(y)**2)
|
| 442 |
+
>>> a
|
| 443 |
+
(x**2 + x)/(x*sin(y)**2 + x*cos(y)**2)
|
| 444 |
+
>>> simplify(a)
|
| 445 |
+
x + 1
|
| 446 |
+
|
| 447 |
+
Note that we could have obtained the same result by using specific
|
| 448 |
+
simplification functions:
|
| 449 |
+
|
| 450 |
+
>>> from sympy import trigsimp, cancel
|
| 451 |
+
>>> trigsimp(a)
|
| 452 |
+
(x**2 + x)/x
|
| 453 |
+
>>> cancel(_)
|
| 454 |
+
x + 1
|
| 455 |
+
|
| 456 |
+
In some cases, applying :func:`simplify` may actually result in some more
|
| 457 |
+
complicated expression. The default ``ratio=1.7`` prevents more extreme
|
| 458 |
+
cases: if (result length)/(input length) > ratio, then input is returned
|
| 459 |
+
unmodified. The ``measure`` parameter lets you specify the function used
|
| 460 |
+
to determine how complex an expression is. The function should take a
|
| 461 |
+
single argument as an expression and return a number such that if
|
| 462 |
+
expression ``a`` is more complex than expression ``b``, then
|
| 463 |
+
``measure(a) > measure(b)``. The default measure function is
|
| 464 |
+
:func:`~.count_ops`, which returns the total number of operations in the
|
| 465 |
+
expression.
|
| 466 |
+
|
| 467 |
+
For example, if ``ratio=1``, ``simplify`` output cannot be longer
|
| 468 |
+
than input.
|
| 469 |
+
|
| 470 |
+
::
|
| 471 |
+
|
| 472 |
+
>>> from sympy import sqrt, simplify, count_ops, oo
|
| 473 |
+
>>> root = 1/(sqrt(2)+3)
|
| 474 |
+
|
| 475 |
+
Since ``simplify(root)`` would result in a slightly longer expression,
|
| 476 |
+
root is returned unchanged instead::
|
| 477 |
+
|
| 478 |
+
>>> simplify(root, ratio=1) == root
|
| 479 |
+
True
|
| 480 |
+
|
| 481 |
+
If ``ratio=oo``, simplify will be applied anyway::
|
| 482 |
+
|
| 483 |
+
>>> count_ops(simplify(root, ratio=oo)) > count_ops(root)
|
| 484 |
+
True
|
| 485 |
+
|
| 486 |
+
Note that the shortest expression is not necessary the simplest, so
|
| 487 |
+
setting ``ratio`` to 1 may not be a good idea.
|
| 488 |
+
Heuristically, the default value ``ratio=1.7`` seems like a reasonable
|
| 489 |
+
choice.
|
| 490 |
+
|
| 491 |
+
You can easily define your own measure function based on what you feel
|
| 492 |
+
should represent the "size" or "complexity" of the input expression. Note
|
| 493 |
+
that some choices, such as ``lambda expr: len(str(expr))`` may appear to be
|
| 494 |
+
good metrics, but have other problems (in this case, the measure function
|
| 495 |
+
may slow down simplify too much for very large expressions). If you do not
|
| 496 |
+
know what a good metric would be, the default, ``count_ops``, is a good
|
| 497 |
+
one.
|
| 498 |
+
|
| 499 |
+
For example:
|
| 500 |
+
|
| 501 |
+
>>> from sympy import symbols, log
|
| 502 |
+
>>> a, b = symbols('a b', positive=True)
|
| 503 |
+
>>> g = log(a) + log(b) + log(a)*log(1/b)
|
| 504 |
+
>>> h = simplify(g)
|
| 505 |
+
>>> h
|
| 506 |
+
log(a*b**(1 - log(a)))
|
| 507 |
+
>>> count_ops(g)
|
| 508 |
+
8
|
| 509 |
+
>>> count_ops(h)
|
| 510 |
+
5
|
| 511 |
+
|
| 512 |
+
So you can see that ``h`` is simpler than ``g`` using the count_ops metric.
|
| 513 |
+
However, we may not like how ``simplify`` (in this case, using
|
| 514 |
+
``logcombine``) has created the ``b**(log(1/a) + 1)`` term. A simple way
|
| 515 |
+
to reduce this would be to give more weight to powers as operations in
|
| 516 |
+
``count_ops``. We can do this by using the ``visual=True`` option:
|
| 517 |
+
|
| 518 |
+
>>> print(count_ops(g, visual=True))
|
| 519 |
+
2*ADD + DIV + 4*LOG + MUL
|
| 520 |
+
>>> print(count_ops(h, visual=True))
|
| 521 |
+
2*LOG + MUL + POW + SUB
|
| 522 |
+
|
| 523 |
+
>>> from sympy import Symbol, S
|
| 524 |
+
>>> def my_measure(expr):
|
| 525 |
+
... POW = Symbol('POW')
|
| 526 |
+
... # Discourage powers by giving POW a weight of 10
|
| 527 |
+
... count = count_ops(expr, visual=True).subs(POW, 10)
|
| 528 |
+
... # Every other operation gets a weight of 1 (the default)
|
| 529 |
+
... count = count.replace(Symbol, type(S.One))
|
| 530 |
+
... return count
|
| 531 |
+
>>> my_measure(g)
|
| 532 |
+
8
|
| 533 |
+
>>> my_measure(h)
|
| 534 |
+
14
|
| 535 |
+
>>> 15./8 > 1.7 # 1.7 is the default ratio
|
| 536 |
+
True
|
| 537 |
+
>>> simplify(g, measure=my_measure)
|
| 538 |
+
-log(a)*log(b) + log(a) + log(b)
|
| 539 |
+
|
| 540 |
+
Note that because ``simplify()`` internally tries many different
|
| 541 |
+
simplification strategies and then compares them using the measure
|
| 542 |
+
function, we get a completely different result that is still different
|
| 543 |
+
from the input expression by doing this.
|
| 544 |
+
|
| 545 |
+
If ``rational=True``, Floats will be recast as Rationals before simplification.
|
| 546 |
+
If ``rational=None``, Floats will be recast as Rationals but the result will
|
| 547 |
+
be recast as Floats. If rational=False(default) then nothing will be done
|
| 548 |
+
to the Floats.
|
| 549 |
+
|
| 550 |
+
If ``inverse=True``, it will be assumed that a composition of inverse
|
| 551 |
+
functions, such as sin and asin, can be cancelled in any order.
|
| 552 |
+
For example, ``asin(sin(x))`` will yield ``x`` without checking whether
|
| 553 |
+
x belongs to the set where this relation is true. The default is
|
| 554 |
+
False.
|
| 555 |
+
|
| 556 |
+
Note that ``simplify()`` automatically calls ``doit()`` on the final
|
| 557 |
+
expression. You can avoid this behavior by passing ``doit=False`` as
|
| 558 |
+
an argument.
|
| 559 |
+
|
| 560 |
+
Also, it should be noted that simplifying a boolean expression is not
|
| 561 |
+
well defined. If the expression prefers automatic evaluation (such as
|
| 562 |
+
:obj:`~.Eq()` or :obj:`~.Or()`), simplification will return ``True`` or
|
| 563 |
+
``False`` if truth value can be determined. If the expression is not
|
| 564 |
+
evaluated by default (such as :obj:`~.Predicate()`), simplification will
|
| 565 |
+
not reduce it and you should use :func:`~.refine()` or :func:`~.ask()`
|
| 566 |
+
function. This inconsistency will be resolved in future version.
|
| 567 |
+
|
| 568 |
+
See Also
|
| 569 |
+
========
|
| 570 |
+
|
| 571 |
+
sympy.assumptions.refine.refine : Simplification using assumptions.
|
| 572 |
+
sympy.assumptions.ask.ask : Query for boolean expressions using assumptions.
|
| 573 |
+
"""
|
| 574 |
+
|
| 575 |
+
def shorter(*choices):
|
| 576 |
+
"""
|
| 577 |
+
Return the choice that has the fewest ops. In case of a tie,
|
| 578 |
+
the expression listed first is selected.
|
| 579 |
+
"""
|
| 580 |
+
if not has_variety(choices):
|
| 581 |
+
return choices[0]
|
| 582 |
+
return min(choices, key=measure)
|
| 583 |
+
|
| 584 |
+
def done(e):
|
| 585 |
+
rv = e.doit() if doit else e
|
| 586 |
+
return shorter(rv, collect_abs(rv))
|
| 587 |
+
|
| 588 |
+
expr = sympify(expr, rational=rational)
|
| 589 |
+
kwargs = {
|
| 590 |
+
"ratio": kwargs.get('ratio', ratio),
|
| 591 |
+
"measure": kwargs.get('measure', measure),
|
| 592 |
+
"rational": kwargs.get('rational', rational),
|
| 593 |
+
"inverse": kwargs.get('inverse', inverse),
|
| 594 |
+
"doit": kwargs.get('doit', doit)}
|
| 595 |
+
# no routine for Expr needs to check for is_zero
|
| 596 |
+
if isinstance(expr, Expr) and expr.is_zero:
|
| 597 |
+
return S.Zero if not expr.is_Number else expr
|
| 598 |
+
|
| 599 |
+
_eval_simplify = getattr(expr, '_eval_simplify', None)
|
| 600 |
+
if _eval_simplify is not None:
|
| 601 |
+
return _eval_simplify(**kwargs)
|
| 602 |
+
|
| 603 |
+
original_expr = expr = collect_abs(signsimp(expr))
|
| 604 |
+
|
| 605 |
+
if not isinstance(expr, Basic) or not expr.args: # XXX: temporary hack
|
| 606 |
+
return expr
|
| 607 |
+
|
| 608 |
+
if inverse and expr.has(Function):
|
| 609 |
+
expr = inversecombine(expr)
|
| 610 |
+
if not expr.args: # simplified to atomic
|
| 611 |
+
return expr
|
| 612 |
+
|
| 613 |
+
# do deep simplification
|
| 614 |
+
handled = Add, Mul, Pow, ExpBase
|
| 615 |
+
expr = expr.replace(
|
| 616 |
+
# here, checking for x.args is not enough because Basic has
|
| 617 |
+
# args but Basic does not always play well with replace, e.g.
|
| 618 |
+
# when simultaneous is True found expressions will be masked
|
| 619 |
+
# off with a Dummy but not all Basic objects in an expression
|
| 620 |
+
# can be replaced with a Dummy
|
| 621 |
+
lambda x: isinstance(x, Expr) and x.args and not isinstance(
|
| 622 |
+
x, handled),
|
| 623 |
+
lambda x: x.func(*[simplify(i, **kwargs) for i in x.args]),
|
| 624 |
+
simultaneous=False)
|
| 625 |
+
if not isinstance(expr, handled):
|
| 626 |
+
return done(expr)
|
| 627 |
+
|
| 628 |
+
if not expr.is_commutative:
|
| 629 |
+
expr = nc_simplify(expr)
|
| 630 |
+
|
| 631 |
+
# TODO: Apply different strategies, considering expression pattern:
|
| 632 |
+
# is it a purely rational function? Is there any trigonometric function?...
|
| 633 |
+
# See also https://github.com/sympy/sympy/pull/185.
|
| 634 |
+
|
| 635 |
+
|
| 636 |
+
# rationalize Floats
|
| 637 |
+
floats = False
|
| 638 |
+
if rational is not False and expr.has(Float):
|
| 639 |
+
floats = True
|
| 640 |
+
expr = nsimplify(expr, rational=True)
|
| 641 |
+
|
| 642 |
+
expr = _bottom_up(expr, lambda w: getattr(w, 'normal', lambda: w)())
|
| 643 |
+
expr = Mul(*powsimp(expr).as_content_primitive())
|
| 644 |
+
_e = cancel(expr)
|
| 645 |
+
expr1 = shorter(_e, _mexpand(_e).cancel()) # issue 6829
|
| 646 |
+
expr2 = shorter(together(expr, deep=True), together(expr1, deep=True))
|
| 647 |
+
|
| 648 |
+
if ratio is S.Infinity:
|
| 649 |
+
expr = expr2
|
| 650 |
+
else:
|
| 651 |
+
expr = shorter(expr2, expr1, expr)
|
| 652 |
+
if not isinstance(expr, Basic): # XXX: temporary hack
|
| 653 |
+
return expr
|
| 654 |
+
|
| 655 |
+
expr = factor_terms(expr, sign=False)
|
| 656 |
+
|
| 657 |
+
# must come before `Piecewise` since this introduces more `Piecewise` terms
|
| 658 |
+
if expr.has(sign):
|
| 659 |
+
expr = expr.rewrite(Abs)
|
| 660 |
+
|
| 661 |
+
# Deal with Piecewise separately to avoid recursive growth of expressions
|
| 662 |
+
if expr.has(Piecewise):
|
| 663 |
+
# Fold into a single Piecewise
|
| 664 |
+
expr = piecewise_fold(expr)
|
| 665 |
+
# Apply doit, if doit=True
|
| 666 |
+
expr = done(expr)
|
| 667 |
+
# Still a Piecewise?
|
| 668 |
+
if expr.has(Piecewise):
|
| 669 |
+
# Fold into a single Piecewise, in case doit lead to some
|
| 670 |
+
# expressions being Piecewise
|
| 671 |
+
expr = piecewise_fold(expr)
|
| 672 |
+
# kroneckersimp also affects Piecewise
|
| 673 |
+
if expr.has(KroneckerDelta):
|
| 674 |
+
expr = kroneckersimp(expr)
|
| 675 |
+
# Still a Piecewise?
|
| 676 |
+
if expr.has(Piecewise):
|
| 677 |
+
# Do not apply doit on the segments as it has already
|
| 678 |
+
# been done above, but simplify
|
| 679 |
+
expr = piecewise_simplify(expr, deep=True, doit=False)
|
| 680 |
+
# Still a Piecewise?
|
| 681 |
+
if expr.has(Piecewise):
|
| 682 |
+
# Try factor common terms
|
| 683 |
+
expr = shorter(expr, factor_terms(expr))
|
| 684 |
+
# As all expressions have been simplified above with the
|
| 685 |
+
# complete simplify, nothing more needs to be done here
|
| 686 |
+
return expr
|
| 687 |
+
|
| 688 |
+
# hyperexpand automatically only works on hypergeometric terms
|
| 689 |
+
# Do this after the Piecewise part to avoid recursive expansion
|
| 690 |
+
expr = hyperexpand(expr)
|
| 691 |
+
|
| 692 |
+
if expr.has(KroneckerDelta):
|
| 693 |
+
expr = kroneckersimp(expr)
|
| 694 |
+
|
| 695 |
+
if expr.has(BesselBase):
|
| 696 |
+
expr = besselsimp(expr)
|
| 697 |
+
|
| 698 |
+
if expr.has(TrigonometricFunction, HyperbolicFunction):
|
| 699 |
+
expr = trigsimp(expr, deep=True)
|
| 700 |
+
|
| 701 |
+
if expr.has(log):
|
| 702 |
+
expr = shorter(expand_log(expr, deep=True), logcombine(expr))
|
| 703 |
+
|
| 704 |
+
if expr.has(CombinatorialFunction, gamma):
|
| 705 |
+
# expression with gamma functions or non-integer arguments is
|
| 706 |
+
# automatically passed to gammasimp
|
| 707 |
+
expr = combsimp(expr)
|
| 708 |
+
|
| 709 |
+
if expr.has(Sum):
|
| 710 |
+
expr = sum_simplify(expr, **kwargs)
|
| 711 |
+
|
| 712 |
+
if expr.has(Integral):
|
| 713 |
+
expr = expr.xreplace({
|
| 714 |
+
i: factor_terms(i) for i in expr.atoms(Integral)})
|
| 715 |
+
|
| 716 |
+
if expr.has(Product):
|
| 717 |
+
expr = product_simplify(expr, **kwargs)
|
| 718 |
+
|
| 719 |
+
from sympy.physics.units import Quantity
|
| 720 |
+
|
| 721 |
+
if expr.has(Quantity):
|
| 722 |
+
from sympy.physics.units.util import quantity_simplify
|
| 723 |
+
expr = quantity_simplify(expr)
|
| 724 |
+
|
| 725 |
+
short = shorter(powsimp(expr, combine='exp', deep=True), powsimp(expr), expr)
|
| 726 |
+
short = shorter(short, cancel(short))
|
| 727 |
+
short = shorter(short, factor_terms(short), expand_power_exp(expand_mul(short)))
|
| 728 |
+
if short.has(TrigonometricFunction, HyperbolicFunction, ExpBase, exp):
|
| 729 |
+
short = exptrigsimp(short)
|
| 730 |
+
|
| 731 |
+
# get rid of hollow 2-arg Mul factorization
|
| 732 |
+
hollow_mul = Transform(
|
| 733 |
+
lambda x: Mul(*x.args),
|
| 734 |
+
lambda x:
|
| 735 |
+
x.is_Mul and
|
| 736 |
+
len(x.args) == 2 and
|
| 737 |
+
x.args[0].is_Number and
|
| 738 |
+
x.args[1].is_Add and
|
| 739 |
+
x.is_commutative)
|
| 740 |
+
expr = short.xreplace(hollow_mul)
|
| 741 |
+
|
| 742 |
+
numer, denom = expr.as_numer_denom()
|
| 743 |
+
if denom.is_Add:
|
| 744 |
+
n, d = fraction(radsimp(1/denom, symbolic=False, max_terms=1))
|
| 745 |
+
if n is not S.One:
|
| 746 |
+
expr = (numer*n).expand()/d
|
| 747 |
+
|
| 748 |
+
if expr.could_extract_minus_sign():
|
| 749 |
+
n, d = fraction(expr)
|
| 750 |
+
if d != 0:
|
| 751 |
+
expr = signsimp(-n/(-d))
|
| 752 |
+
|
| 753 |
+
if measure(expr) > ratio*measure(original_expr):
|
| 754 |
+
expr = original_expr
|
| 755 |
+
|
| 756 |
+
# restore floats
|
| 757 |
+
if floats and rational is None:
|
| 758 |
+
expr = nfloat(expr, exponent=False)
|
| 759 |
+
|
| 760 |
+
return done(expr)
|
| 761 |
+
|
| 762 |
+
|
| 763 |
+
def sum_simplify(s, **kwargs):
|
| 764 |
+
"""Main function for Sum simplification"""
|
| 765 |
+
if not isinstance(s, Add):
|
| 766 |
+
s = s.xreplace({a: sum_simplify(a, **kwargs)
|
| 767 |
+
for a in s.atoms(Add) if a.has(Sum)})
|
| 768 |
+
s = expand(s)
|
| 769 |
+
if not isinstance(s, Add):
|
| 770 |
+
return s
|
| 771 |
+
|
| 772 |
+
terms = s.args
|
| 773 |
+
s_t = [] # Sum Terms
|
| 774 |
+
o_t = [] # Other Terms
|
| 775 |
+
|
| 776 |
+
for term in terms:
|
| 777 |
+
sum_terms, other = sift(Mul.make_args(term),
|
| 778 |
+
lambda i: isinstance(i, Sum), binary=True)
|
| 779 |
+
if not sum_terms:
|
| 780 |
+
o_t.append(term)
|
| 781 |
+
continue
|
| 782 |
+
other = [Mul(*other)]
|
| 783 |
+
s_t.append(Mul(*(other + [s._eval_simplify(**kwargs) for s in sum_terms])))
|
| 784 |
+
|
| 785 |
+
result = Add(sum_combine(s_t), *o_t)
|
| 786 |
+
|
| 787 |
+
return result
|
| 788 |
+
|
| 789 |
+
|
| 790 |
+
def sum_combine(s_t):
|
| 791 |
+
"""Helper function for Sum simplification
|
| 792 |
+
|
| 793 |
+
Attempts to simplify a list of sums, by combining limits / sum function's
|
| 794 |
+
returns the simplified sum
|
| 795 |
+
"""
|
| 796 |
+
used = [False] * len(s_t)
|
| 797 |
+
|
| 798 |
+
for method in range(2):
|
| 799 |
+
for i, s_term1 in enumerate(s_t):
|
| 800 |
+
if not used[i]:
|
| 801 |
+
for j, s_term2 in enumerate(s_t):
|
| 802 |
+
if not used[j] and i != j:
|
| 803 |
+
temp = sum_add(s_term1, s_term2, method)
|
| 804 |
+
if isinstance(temp, (Sum, Mul)):
|
| 805 |
+
s_t[i] = temp
|
| 806 |
+
s_term1 = s_t[i]
|
| 807 |
+
used[j] = True
|
| 808 |
+
|
| 809 |
+
result = S.Zero
|
| 810 |
+
for i, s_term in enumerate(s_t):
|
| 811 |
+
if not used[i]:
|
| 812 |
+
result = Add(result, s_term)
|
| 813 |
+
|
| 814 |
+
return result
|
| 815 |
+
|
| 816 |
+
|
| 817 |
+
def factor_sum(self, limits=None, radical=False, clear=False, fraction=False, sign=True):
|
| 818 |
+
"""Return Sum with constant factors extracted.
|
| 819 |
+
|
| 820 |
+
If ``limits`` is specified then ``self`` is the summand; the other
|
| 821 |
+
keywords are passed to ``factor_terms``.
|
| 822 |
+
|
| 823 |
+
Examples
|
| 824 |
+
========
|
| 825 |
+
|
| 826 |
+
>>> from sympy import Sum
|
| 827 |
+
>>> from sympy.abc import x, y
|
| 828 |
+
>>> from sympy.simplify.simplify import factor_sum
|
| 829 |
+
>>> s = Sum(x*y, (x, 1, 3))
|
| 830 |
+
>>> factor_sum(s)
|
| 831 |
+
y*Sum(x, (x, 1, 3))
|
| 832 |
+
>>> factor_sum(s.function, s.limits)
|
| 833 |
+
y*Sum(x, (x, 1, 3))
|
| 834 |
+
"""
|
| 835 |
+
# XXX deprecate in favor of direct call to factor_terms
|
| 836 |
+
kwargs = {"radical": radical, "clear": clear,
|
| 837 |
+
"fraction": fraction, "sign": sign}
|
| 838 |
+
expr = Sum(self, *limits) if limits else self
|
| 839 |
+
return factor_terms(expr, **kwargs)
|
| 840 |
+
|
| 841 |
+
|
| 842 |
+
def sum_add(self, other, method=0):
|
| 843 |
+
"""Helper function for Sum simplification"""
|
| 844 |
+
#we know this is something in terms of a constant * a sum
|
| 845 |
+
#so we temporarily put the constants inside for simplification
|
| 846 |
+
#then simplify the result
|
| 847 |
+
def __refactor(val):
|
| 848 |
+
args = Mul.make_args(val)
|
| 849 |
+
sumv = next(x for x in args if isinstance(x, Sum))
|
| 850 |
+
constant = Mul(*[x for x in args if x != sumv])
|
| 851 |
+
return Sum(constant * sumv.function, *sumv.limits)
|
| 852 |
+
|
| 853 |
+
if isinstance(self, Mul):
|
| 854 |
+
rself = __refactor(self)
|
| 855 |
+
else:
|
| 856 |
+
rself = self
|
| 857 |
+
|
| 858 |
+
if isinstance(other, Mul):
|
| 859 |
+
rother = __refactor(other)
|
| 860 |
+
else:
|
| 861 |
+
rother = other
|
| 862 |
+
|
| 863 |
+
if type(rself) is type(rother):
|
| 864 |
+
if method == 0:
|
| 865 |
+
if rself.limits == rother.limits:
|
| 866 |
+
return factor_sum(Sum(rself.function + rother.function, *rself.limits))
|
| 867 |
+
elif method == 1:
|
| 868 |
+
if simplify(rself.function - rother.function) == 0:
|
| 869 |
+
if len(rself.limits) == len(rother.limits) == 1:
|
| 870 |
+
i = rself.limits[0][0]
|
| 871 |
+
x1 = rself.limits[0][1]
|
| 872 |
+
y1 = rself.limits[0][2]
|
| 873 |
+
j = rother.limits[0][0]
|
| 874 |
+
x2 = rother.limits[0][1]
|
| 875 |
+
y2 = rother.limits[0][2]
|
| 876 |
+
|
| 877 |
+
if i == j:
|
| 878 |
+
if x2 == y1 + 1:
|
| 879 |
+
return factor_sum(Sum(rself.function, (i, x1, y2)))
|
| 880 |
+
elif x1 == y2 + 1:
|
| 881 |
+
return factor_sum(Sum(rself.function, (i, x2, y1)))
|
| 882 |
+
|
| 883 |
+
return Add(self, other)
|
| 884 |
+
|
| 885 |
+
|
| 886 |
+
def product_simplify(s, **kwargs):
|
| 887 |
+
"""Main function for Product simplification"""
|
| 888 |
+
terms = Mul.make_args(s)
|
| 889 |
+
p_t = [] # Product Terms
|
| 890 |
+
o_t = [] # Other Terms
|
| 891 |
+
|
| 892 |
+
deep = kwargs.get('deep', True)
|
| 893 |
+
for term in terms:
|
| 894 |
+
if isinstance(term, Product):
|
| 895 |
+
if deep:
|
| 896 |
+
p_t.append(Product(term.function.simplify(**kwargs),
|
| 897 |
+
*term.limits))
|
| 898 |
+
else:
|
| 899 |
+
p_t.append(term)
|
| 900 |
+
else:
|
| 901 |
+
o_t.append(term)
|
| 902 |
+
|
| 903 |
+
used = [False] * len(p_t)
|
| 904 |
+
|
| 905 |
+
for method in range(2):
|
| 906 |
+
for i, p_term1 in enumerate(p_t):
|
| 907 |
+
if not used[i]:
|
| 908 |
+
for j, p_term2 in enumerate(p_t):
|
| 909 |
+
if not used[j] and i != j:
|
| 910 |
+
tmp_prod = product_mul(p_term1, p_term2, method)
|
| 911 |
+
if isinstance(tmp_prod, Product):
|
| 912 |
+
p_t[i] = tmp_prod
|
| 913 |
+
used[j] = True
|
| 914 |
+
|
| 915 |
+
result = Mul(*o_t)
|
| 916 |
+
|
| 917 |
+
for i, p_term in enumerate(p_t):
|
| 918 |
+
if not used[i]:
|
| 919 |
+
result = Mul(result, p_term)
|
| 920 |
+
|
| 921 |
+
return result
|
| 922 |
+
|
| 923 |
+
|
| 924 |
+
def product_mul(self, other, method=0):
|
| 925 |
+
"""Helper function for Product simplification"""
|
| 926 |
+
if type(self) is type(other):
|
| 927 |
+
if method == 0:
|
| 928 |
+
if self.limits == other.limits:
|
| 929 |
+
return Product(self.function * other.function, *self.limits)
|
| 930 |
+
elif method == 1:
|
| 931 |
+
if simplify(self.function - other.function) == 0:
|
| 932 |
+
if len(self.limits) == len(other.limits) == 1:
|
| 933 |
+
i = self.limits[0][0]
|
| 934 |
+
x1 = self.limits[0][1]
|
| 935 |
+
y1 = self.limits[0][2]
|
| 936 |
+
j = other.limits[0][0]
|
| 937 |
+
x2 = other.limits[0][1]
|
| 938 |
+
y2 = other.limits[0][2]
|
| 939 |
+
|
| 940 |
+
if i == j:
|
| 941 |
+
if x2 == y1 + 1:
|
| 942 |
+
return Product(self.function, (i, x1, y2))
|
| 943 |
+
elif x1 == y2 + 1:
|
| 944 |
+
return Product(self.function, (i, x2, y1))
|
| 945 |
+
|
| 946 |
+
return Mul(self, other)
|
| 947 |
+
|
| 948 |
+
|
| 949 |
+
def _nthroot_solve(p, n, prec):
|
| 950 |
+
"""
|
| 951 |
+
helper function for ``nthroot``
|
| 952 |
+
It denests ``p**Rational(1, n)`` using its minimal polynomial
|
| 953 |
+
"""
|
| 954 |
+
from sympy.solvers import solve
|
| 955 |
+
while n % 2 == 0:
|
| 956 |
+
p = sqrtdenest(sqrt(p))
|
| 957 |
+
n = n // 2
|
| 958 |
+
if n == 1:
|
| 959 |
+
return p
|
| 960 |
+
pn = p**Rational(1, n)
|
| 961 |
+
x = Symbol('x')
|
| 962 |
+
f = _minimal_polynomial_sq(p, n, x)
|
| 963 |
+
if f is None:
|
| 964 |
+
return None
|
| 965 |
+
sols = solve(f, x)
|
| 966 |
+
for sol in sols:
|
| 967 |
+
if abs(sol - pn).n() < 1./10**prec:
|
| 968 |
+
sol = sqrtdenest(sol)
|
| 969 |
+
if _mexpand(sol**n) == p:
|
| 970 |
+
return sol
|
| 971 |
+
|
| 972 |
+
|
| 973 |
+
def logcombine(expr, force=False):
|
| 974 |
+
"""
|
| 975 |
+
Takes logarithms and combines them using the following rules:
|
| 976 |
+
|
| 977 |
+
- log(x) + log(y) == log(x*y) if both are positive
|
| 978 |
+
- a*log(x) == log(x**a) if x is positive and a is real
|
| 979 |
+
|
| 980 |
+
If ``force`` is ``True`` then the assumptions above will be assumed to hold if
|
| 981 |
+
there is no assumption already in place on a quantity. For example, if
|
| 982 |
+
``a`` is imaginary or the argument negative, force will not perform a
|
| 983 |
+
combination but if ``a`` is a symbol with no assumptions the change will
|
| 984 |
+
take place.
|
| 985 |
+
|
| 986 |
+
Examples
|
| 987 |
+
========
|
| 988 |
+
|
| 989 |
+
>>> from sympy import Symbol, symbols, log, logcombine, I
|
| 990 |
+
>>> from sympy.abc import a, x, y, z
|
| 991 |
+
>>> logcombine(a*log(x) + log(y) - log(z))
|
| 992 |
+
a*log(x) + log(y) - log(z)
|
| 993 |
+
>>> logcombine(a*log(x) + log(y) - log(z), force=True)
|
| 994 |
+
log(x**a*y/z)
|
| 995 |
+
>>> x,y,z = symbols('x,y,z', positive=True)
|
| 996 |
+
>>> a = Symbol('a', real=True)
|
| 997 |
+
>>> logcombine(a*log(x) + log(y) - log(z))
|
| 998 |
+
log(x**a*y/z)
|
| 999 |
+
|
| 1000 |
+
The transformation is limited to factors and/or terms that
|
| 1001 |
+
contain logs, so the result depends on the initial state of
|
| 1002 |
+
expansion:
|
| 1003 |
+
|
| 1004 |
+
>>> eq = (2 + 3*I)*log(x)
|
| 1005 |
+
>>> logcombine(eq, force=True) == eq
|
| 1006 |
+
True
|
| 1007 |
+
>>> logcombine(eq.expand(), force=True)
|
| 1008 |
+
log(x**2) + I*log(x**3)
|
| 1009 |
+
|
| 1010 |
+
See Also
|
| 1011 |
+
========
|
| 1012 |
+
|
| 1013 |
+
posify: replace all symbols with symbols having positive assumptions
|
| 1014 |
+
sympy.core.function.expand_log: expand the logarithms of products
|
| 1015 |
+
and powers; the opposite of logcombine
|
| 1016 |
+
|
| 1017 |
+
"""
|
| 1018 |
+
|
| 1019 |
+
def f(rv):
|
| 1020 |
+
if not (rv.is_Add or rv.is_Mul):
|
| 1021 |
+
return rv
|
| 1022 |
+
|
| 1023 |
+
def gooda(a):
|
| 1024 |
+
# bool to tell whether the leading ``a`` in ``a*log(x)``
|
| 1025 |
+
# could appear as log(x**a)
|
| 1026 |
+
return (a is not S.NegativeOne and # -1 *could* go, but we disallow
|
| 1027 |
+
(a.is_extended_real or force and a.is_extended_real is not False))
|
| 1028 |
+
|
| 1029 |
+
def goodlog(l):
|
| 1030 |
+
# bool to tell whether log ``l``'s argument can combine with others
|
| 1031 |
+
a = l.args[0]
|
| 1032 |
+
return a.is_positive or force and a.is_nonpositive is not False
|
| 1033 |
+
|
| 1034 |
+
other = []
|
| 1035 |
+
logs = []
|
| 1036 |
+
log1 = defaultdict(list)
|
| 1037 |
+
for a in Add.make_args(rv):
|
| 1038 |
+
if isinstance(a, log) and goodlog(a):
|
| 1039 |
+
log1[()].append(([], a))
|
| 1040 |
+
elif not a.is_Mul:
|
| 1041 |
+
other.append(a)
|
| 1042 |
+
else:
|
| 1043 |
+
ot = []
|
| 1044 |
+
co = []
|
| 1045 |
+
lo = []
|
| 1046 |
+
for ai in a.args:
|
| 1047 |
+
if ai.is_Rational and ai < 0:
|
| 1048 |
+
ot.append(S.NegativeOne)
|
| 1049 |
+
co.append(-ai)
|
| 1050 |
+
elif isinstance(ai, log) and goodlog(ai):
|
| 1051 |
+
lo.append(ai)
|
| 1052 |
+
elif gooda(ai):
|
| 1053 |
+
co.append(ai)
|
| 1054 |
+
else:
|
| 1055 |
+
ot.append(ai)
|
| 1056 |
+
if len(lo) > 1:
|
| 1057 |
+
logs.append((ot, co, lo))
|
| 1058 |
+
elif lo:
|
| 1059 |
+
log1[tuple(ot)].append((co, lo[0]))
|
| 1060 |
+
else:
|
| 1061 |
+
other.append(a)
|
| 1062 |
+
|
| 1063 |
+
# if there is only one log in other, put it with the
|
| 1064 |
+
# good logs
|
| 1065 |
+
if len(other) == 1 and isinstance(other[0], log):
|
| 1066 |
+
log1[()].append(([], other.pop()))
|
| 1067 |
+
# if there is only one log at each coefficient and none have
|
| 1068 |
+
# an exponent to place inside the log then there is nothing to do
|
| 1069 |
+
if not logs and all(len(log1[k]) == 1 and log1[k][0] == [] for k in log1):
|
| 1070 |
+
return rv
|
| 1071 |
+
|
| 1072 |
+
# collapse multi-logs as far as possible in a canonical way
|
| 1073 |
+
# TODO: see if x*log(a)+x*log(a)*log(b) -> x*log(a)*(1+log(b))?
|
| 1074 |
+
# -- in this case, it's unambiguous, but if it were were a log(c) in
|
| 1075 |
+
# each term then it's arbitrary whether they are grouped by log(a) or
|
| 1076 |
+
# by log(c). So for now, just leave this alone; it's probably better to
|
| 1077 |
+
# let the user decide
|
| 1078 |
+
for o, e, l in logs:
|
| 1079 |
+
l = list(ordered(l))
|
| 1080 |
+
e = log(l.pop(0).args[0]**Mul(*e))
|
| 1081 |
+
while l:
|
| 1082 |
+
li = l.pop(0)
|
| 1083 |
+
e = log(li.args[0]**e)
|
| 1084 |
+
c, l = Mul(*o), e
|
| 1085 |
+
if isinstance(l, log): # it should be, but check to be sure
|
| 1086 |
+
log1[(c,)].append(([], l))
|
| 1087 |
+
else:
|
| 1088 |
+
other.append(c*l)
|
| 1089 |
+
|
| 1090 |
+
# logs that have the same coefficient can multiply
|
| 1091 |
+
for k in list(log1.keys()):
|
| 1092 |
+
log1[Mul(*k)] = log(logcombine(Mul(*[
|
| 1093 |
+
l.args[0]**Mul(*c) for c, l in log1.pop(k)]),
|
| 1094 |
+
force=force), evaluate=False)
|
| 1095 |
+
|
| 1096 |
+
# logs that have oppositely signed coefficients can divide
|
| 1097 |
+
for k in ordered(list(log1.keys())):
|
| 1098 |
+
if k not in log1: # already popped as -k
|
| 1099 |
+
continue
|
| 1100 |
+
if -k in log1:
|
| 1101 |
+
# figure out which has the minus sign; the one with
|
| 1102 |
+
# more op counts should be the one
|
| 1103 |
+
num, den = k, -k
|
| 1104 |
+
if num.count_ops() > den.count_ops():
|
| 1105 |
+
num, den = den, num
|
| 1106 |
+
other.append(
|
| 1107 |
+
num*log(log1.pop(num).args[0]/log1.pop(den).args[0],
|
| 1108 |
+
evaluate=False))
|
| 1109 |
+
else:
|
| 1110 |
+
other.append(k*log1.pop(k))
|
| 1111 |
+
|
| 1112 |
+
return Add(*other)
|
| 1113 |
+
|
| 1114 |
+
return _bottom_up(expr, f)
|
| 1115 |
+
|
| 1116 |
+
|
| 1117 |
+
def inversecombine(expr):
|
| 1118 |
+
"""Simplify the composition of a function and its inverse.
|
| 1119 |
+
|
| 1120 |
+
Explanation
|
| 1121 |
+
===========
|
| 1122 |
+
|
| 1123 |
+
No attention is paid to whether the inverse is a left inverse or a
|
| 1124 |
+
right inverse; thus, the result will in general not be equivalent
|
| 1125 |
+
to the original expression.
|
| 1126 |
+
|
| 1127 |
+
Examples
|
| 1128 |
+
========
|
| 1129 |
+
|
| 1130 |
+
>>> from sympy.simplify.simplify import inversecombine
|
| 1131 |
+
>>> from sympy import asin, sin, log, exp
|
| 1132 |
+
>>> from sympy.abc import x
|
| 1133 |
+
>>> inversecombine(asin(sin(x)))
|
| 1134 |
+
x
|
| 1135 |
+
>>> inversecombine(2*log(exp(3*x)))
|
| 1136 |
+
6*x
|
| 1137 |
+
"""
|
| 1138 |
+
|
| 1139 |
+
def f(rv):
|
| 1140 |
+
if isinstance(rv, log):
|
| 1141 |
+
if isinstance(rv.args[0], exp) or (rv.args[0].is_Pow and rv.args[0].base == S.Exp1):
|
| 1142 |
+
rv = rv.args[0].exp
|
| 1143 |
+
elif rv.is_Function and hasattr(rv, "inverse"):
|
| 1144 |
+
if (len(rv.args) == 1 and len(rv.args[0].args) == 1 and
|
| 1145 |
+
isinstance(rv.args[0], rv.inverse(argindex=1))):
|
| 1146 |
+
rv = rv.args[0].args[0]
|
| 1147 |
+
if rv.is_Pow and rv.base == S.Exp1:
|
| 1148 |
+
if isinstance(rv.exp, log):
|
| 1149 |
+
rv = rv.exp.args[0]
|
| 1150 |
+
return rv
|
| 1151 |
+
|
| 1152 |
+
return _bottom_up(expr, f)
|
| 1153 |
+
|
| 1154 |
+
|
| 1155 |
+
def kroneckersimp(expr):
|
| 1156 |
+
"""
|
| 1157 |
+
Simplify expressions with KroneckerDelta.
|
| 1158 |
+
|
| 1159 |
+
The only simplification currently attempted is to identify multiplicative cancellation:
|
| 1160 |
+
|
| 1161 |
+
Examples
|
| 1162 |
+
========
|
| 1163 |
+
|
| 1164 |
+
>>> from sympy import KroneckerDelta, kroneckersimp
|
| 1165 |
+
>>> from sympy.abc import i
|
| 1166 |
+
>>> kroneckersimp(1 + KroneckerDelta(0, i) * KroneckerDelta(1, i))
|
| 1167 |
+
1
|
| 1168 |
+
"""
|
| 1169 |
+
def args_cancel(args1, args2):
|
| 1170 |
+
for i1 in range(2):
|
| 1171 |
+
for i2 in range(2):
|
| 1172 |
+
a1 = args1[i1]
|
| 1173 |
+
a2 = args2[i2]
|
| 1174 |
+
a3 = args1[(i1 + 1) % 2]
|
| 1175 |
+
a4 = args2[(i2 + 1) % 2]
|
| 1176 |
+
if Eq(a1, a2) is S.true and Eq(a3, a4) is S.false:
|
| 1177 |
+
return True
|
| 1178 |
+
return False
|
| 1179 |
+
|
| 1180 |
+
def cancel_kronecker_mul(m):
|
| 1181 |
+
args = m.args
|
| 1182 |
+
deltas = [a for a in args if isinstance(a, KroneckerDelta)]
|
| 1183 |
+
for delta1, delta2 in subsets(deltas, 2):
|
| 1184 |
+
args1 = delta1.args
|
| 1185 |
+
args2 = delta2.args
|
| 1186 |
+
if args_cancel(args1, args2):
|
| 1187 |
+
return S.Zero * m # In case of oo etc
|
| 1188 |
+
return m
|
| 1189 |
+
|
| 1190 |
+
if not expr.has(KroneckerDelta):
|
| 1191 |
+
return expr
|
| 1192 |
+
|
| 1193 |
+
if expr.has(Piecewise):
|
| 1194 |
+
expr = expr.rewrite(KroneckerDelta)
|
| 1195 |
+
|
| 1196 |
+
newexpr = expr
|
| 1197 |
+
expr = None
|
| 1198 |
+
|
| 1199 |
+
while newexpr != expr:
|
| 1200 |
+
expr = newexpr
|
| 1201 |
+
newexpr = expr.replace(lambda e: isinstance(e, Mul), cancel_kronecker_mul)
|
| 1202 |
+
|
| 1203 |
+
return expr
|
| 1204 |
+
|
| 1205 |
+
|
| 1206 |
+
def besselsimp(expr):
|
| 1207 |
+
"""
|
| 1208 |
+
Simplify bessel-type functions.
|
| 1209 |
+
|
| 1210 |
+
Explanation
|
| 1211 |
+
===========
|
| 1212 |
+
|
| 1213 |
+
This routine tries to simplify bessel-type functions. Currently it only
|
| 1214 |
+
works on the Bessel J and I functions, however. It works by looking at all
|
| 1215 |
+
such functions in turn, and eliminating factors of "I" and "-1" (actually
|
| 1216 |
+
their polar equivalents) in front of the argument. Then, functions of
|
| 1217 |
+
half-integer order are rewritten using trigonometric functions and
|
| 1218 |
+
functions of integer order (> 1) are rewritten using functions
|
| 1219 |
+
of low order. Finally, if the expression was changed, compute
|
| 1220 |
+
factorization of the result with factor().
|
| 1221 |
+
|
| 1222 |
+
>>> from sympy import besselj, besseli, besselsimp, polar_lift, I, S
|
| 1223 |
+
>>> from sympy.abc import z, nu
|
| 1224 |
+
>>> besselsimp(besselj(nu, z*polar_lift(-1)))
|
| 1225 |
+
exp(I*pi*nu)*besselj(nu, z)
|
| 1226 |
+
>>> besselsimp(besseli(nu, z*polar_lift(-I)))
|
| 1227 |
+
exp(-I*pi*nu/2)*besselj(nu, z)
|
| 1228 |
+
>>> besselsimp(besseli(S(-1)/2, z))
|
| 1229 |
+
sqrt(2)*cosh(z)/(sqrt(pi)*sqrt(z))
|
| 1230 |
+
>>> besselsimp(z*besseli(0, z) + z*(besseli(2, z))/2 + besseli(1, z))
|
| 1231 |
+
3*z*besseli(0, z)/2
|
| 1232 |
+
"""
|
| 1233 |
+
# TODO
|
| 1234 |
+
# - better algorithm?
|
| 1235 |
+
# - simplify (cos(pi*b)*besselj(b,z) - besselj(-b,z))/sin(pi*b) ...
|
| 1236 |
+
# - use contiguity relations?
|
| 1237 |
+
|
| 1238 |
+
def replacer(fro, to, factors):
|
| 1239 |
+
factors = set(factors)
|
| 1240 |
+
|
| 1241 |
+
def repl(nu, z):
|
| 1242 |
+
if factors.intersection(Mul.make_args(z)):
|
| 1243 |
+
return to(nu, z)
|
| 1244 |
+
return fro(nu, z)
|
| 1245 |
+
return repl
|
| 1246 |
+
|
| 1247 |
+
def torewrite(fro, to):
|
| 1248 |
+
def tofunc(nu, z):
|
| 1249 |
+
return fro(nu, z).rewrite(to)
|
| 1250 |
+
return tofunc
|
| 1251 |
+
|
| 1252 |
+
def tominus(fro):
|
| 1253 |
+
def tofunc(nu, z):
|
| 1254 |
+
return exp(I*pi*nu)*fro(nu, exp_polar(-I*pi)*z)
|
| 1255 |
+
return tofunc
|
| 1256 |
+
|
| 1257 |
+
orig_expr = expr
|
| 1258 |
+
|
| 1259 |
+
ifactors = [I, exp_polar(I*pi/2), exp_polar(-I*pi/2)]
|
| 1260 |
+
expr = expr.replace(
|
| 1261 |
+
besselj, replacer(besselj,
|
| 1262 |
+
torewrite(besselj, besseli), ifactors))
|
| 1263 |
+
expr = expr.replace(
|
| 1264 |
+
besseli, replacer(besseli,
|
| 1265 |
+
torewrite(besseli, besselj), ifactors))
|
| 1266 |
+
|
| 1267 |
+
minusfactors = [-1, exp_polar(I*pi)]
|
| 1268 |
+
expr = expr.replace(
|
| 1269 |
+
besselj, replacer(besselj, tominus(besselj), minusfactors))
|
| 1270 |
+
expr = expr.replace(
|
| 1271 |
+
besseli, replacer(besseli, tominus(besseli), minusfactors))
|
| 1272 |
+
|
| 1273 |
+
z0 = Dummy('z')
|
| 1274 |
+
|
| 1275 |
+
def expander(fro):
|
| 1276 |
+
def repl(nu, z):
|
| 1277 |
+
if (nu % 1) == S.Half:
|
| 1278 |
+
return simplify(trigsimp(unpolarify(
|
| 1279 |
+
fro(nu, z0).rewrite(besselj).rewrite(jn).expand(
|
| 1280 |
+
func=True)).subs(z0, z)))
|
| 1281 |
+
elif nu.is_Integer and nu > 1:
|
| 1282 |
+
return fro(nu, z).expand(func=True)
|
| 1283 |
+
return fro(nu, z)
|
| 1284 |
+
return repl
|
| 1285 |
+
|
| 1286 |
+
expr = expr.replace(besselj, expander(besselj))
|
| 1287 |
+
expr = expr.replace(bessely, expander(bessely))
|
| 1288 |
+
expr = expr.replace(besseli, expander(besseli))
|
| 1289 |
+
expr = expr.replace(besselk, expander(besselk))
|
| 1290 |
+
|
| 1291 |
+
def _bessel_simp_recursion(expr):
|
| 1292 |
+
|
| 1293 |
+
def _use_recursion(bessel, expr):
|
| 1294 |
+
while True:
|
| 1295 |
+
bessels = expr.find(lambda x: isinstance(x, bessel))
|
| 1296 |
+
try:
|
| 1297 |
+
for ba in sorted(bessels, key=lambda x: re(x.args[0])):
|
| 1298 |
+
a, x = ba.args
|
| 1299 |
+
bap1 = bessel(a+1, x)
|
| 1300 |
+
bap2 = bessel(a+2, x)
|
| 1301 |
+
if expr.has(bap1) and expr.has(bap2):
|
| 1302 |
+
expr = expr.subs(ba, 2*(a+1)/x*bap1 - bap2)
|
| 1303 |
+
break
|
| 1304 |
+
else:
|
| 1305 |
+
return expr
|
| 1306 |
+
except (ValueError, TypeError):
|
| 1307 |
+
return expr
|
| 1308 |
+
if expr.has(besselj):
|
| 1309 |
+
expr = _use_recursion(besselj, expr)
|
| 1310 |
+
if expr.has(bessely):
|
| 1311 |
+
expr = _use_recursion(bessely, expr)
|
| 1312 |
+
return expr
|
| 1313 |
+
|
| 1314 |
+
expr = _bessel_simp_recursion(expr)
|
| 1315 |
+
if expr != orig_expr:
|
| 1316 |
+
expr = expr.factor()
|
| 1317 |
+
|
| 1318 |
+
return expr
|
| 1319 |
+
|
| 1320 |
+
|
| 1321 |
+
def nthroot(expr, n, max_len=4, prec=15):
|
| 1322 |
+
"""
|
| 1323 |
+
Compute a real nth-root of a sum of surds.
|
| 1324 |
+
|
| 1325 |
+
Parameters
|
| 1326 |
+
==========
|
| 1327 |
+
|
| 1328 |
+
expr : sum of surds
|
| 1329 |
+
n : integer
|
| 1330 |
+
max_len : maximum number of surds passed as constants to ``nsimplify``
|
| 1331 |
+
|
| 1332 |
+
Algorithm
|
| 1333 |
+
=========
|
| 1334 |
+
|
| 1335 |
+
First ``nsimplify`` is used to get a candidate root; if it is not a
|
| 1336 |
+
root the minimal polynomial is computed; the answer is one of its
|
| 1337 |
+
roots.
|
| 1338 |
+
|
| 1339 |
+
Examples
|
| 1340 |
+
========
|
| 1341 |
+
|
| 1342 |
+
>>> from sympy.simplify.simplify import nthroot
|
| 1343 |
+
>>> from sympy import sqrt
|
| 1344 |
+
>>> nthroot(90 + 34*sqrt(7), 3)
|
| 1345 |
+
sqrt(7) + 3
|
| 1346 |
+
|
| 1347 |
+
"""
|
| 1348 |
+
expr = sympify(expr)
|
| 1349 |
+
n = sympify(n)
|
| 1350 |
+
p = expr**Rational(1, n)
|
| 1351 |
+
if not n.is_integer:
|
| 1352 |
+
return p
|
| 1353 |
+
if not _is_sum_surds(expr):
|
| 1354 |
+
return p
|
| 1355 |
+
surds = []
|
| 1356 |
+
coeff_muls = [x.as_coeff_Mul() for x in expr.args]
|
| 1357 |
+
for x, y in coeff_muls:
|
| 1358 |
+
if not x.is_rational:
|
| 1359 |
+
return p
|
| 1360 |
+
if y is S.One:
|
| 1361 |
+
continue
|
| 1362 |
+
if not (y.is_Pow and y.exp == S.Half and y.base.is_integer):
|
| 1363 |
+
return p
|
| 1364 |
+
surds.append(y)
|
| 1365 |
+
surds.sort()
|
| 1366 |
+
surds = surds[:max_len]
|
| 1367 |
+
if expr < 0 and n % 2 == 1:
|
| 1368 |
+
p = (-expr)**Rational(1, n)
|
| 1369 |
+
a = nsimplify(p, constants=surds)
|
| 1370 |
+
res = a if _mexpand(a**n) == _mexpand(-expr) else p
|
| 1371 |
+
return -res
|
| 1372 |
+
a = nsimplify(p, constants=surds)
|
| 1373 |
+
if _mexpand(a) is not _mexpand(p) and _mexpand(a**n) == _mexpand(expr):
|
| 1374 |
+
return _mexpand(a)
|
| 1375 |
+
expr = _nthroot_solve(expr, n, prec)
|
| 1376 |
+
if expr is None:
|
| 1377 |
+
return p
|
| 1378 |
+
return expr
|
| 1379 |
+
|
| 1380 |
+
|
| 1381 |
+
def nsimplify(expr, constants=(), tolerance=None, full=False, rational=None,
|
| 1382 |
+
rational_conversion='base10'):
|
| 1383 |
+
"""
|
| 1384 |
+
Find a simple representation for a number or, if there are free symbols or
|
| 1385 |
+
if ``rational=True``, then replace Floats with their Rational equivalents. If
|
| 1386 |
+
no change is made and rational is not False then Floats will at least be
|
| 1387 |
+
converted to Rationals.
|
| 1388 |
+
|
| 1389 |
+
Explanation
|
| 1390 |
+
===========
|
| 1391 |
+
|
| 1392 |
+
For numerical expressions, a simple formula that numerically matches the
|
| 1393 |
+
given numerical expression is sought (and the input should be possible
|
| 1394 |
+
to evalf to a precision of at least 30 digits).
|
| 1395 |
+
|
| 1396 |
+
Optionally, a list of (rationally independent) constants to
|
| 1397 |
+
include in the formula may be given.
|
| 1398 |
+
|
| 1399 |
+
A lower tolerance may be set to find less exact matches. If no tolerance
|
| 1400 |
+
is given then the least precise value will set the tolerance (e.g. Floats
|
| 1401 |
+
default to 15 digits of precision, so would be tolerance=10**-15).
|
| 1402 |
+
|
| 1403 |
+
With ``full=True``, a more extensive search is performed
|
| 1404 |
+
(this is useful to find simpler numbers when the tolerance
|
| 1405 |
+
is set low).
|
| 1406 |
+
|
| 1407 |
+
When converting to rational, if rational_conversion='base10' (the default), then
|
| 1408 |
+
convert floats to rationals using their base-10 (string) representation.
|
| 1409 |
+
When rational_conversion='exact' it uses the exact, base-2 representation.
|
| 1410 |
+
|
| 1411 |
+
Examples
|
| 1412 |
+
========
|
| 1413 |
+
|
| 1414 |
+
>>> from sympy import nsimplify, sqrt, GoldenRatio, exp, I, pi
|
| 1415 |
+
>>> nsimplify(4/(1+sqrt(5)), [GoldenRatio])
|
| 1416 |
+
-2 + 2*GoldenRatio
|
| 1417 |
+
>>> nsimplify((1/(exp(3*pi*I/5)+1)))
|
| 1418 |
+
1/2 - I*sqrt(sqrt(5)/10 + 1/4)
|
| 1419 |
+
>>> nsimplify(I**I, [pi])
|
| 1420 |
+
exp(-pi/2)
|
| 1421 |
+
>>> nsimplify(pi, tolerance=0.01)
|
| 1422 |
+
22/7
|
| 1423 |
+
|
| 1424 |
+
>>> nsimplify(0.333333333333333, rational=True, rational_conversion='exact')
|
| 1425 |
+
6004799503160655/18014398509481984
|
| 1426 |
+
>>> nsimplify(0.333333333333333, rational=True)
|
| 1427 |
+
1/3
|
| 1428 |
+
|
| 1429 |
+
See Also
|
| 1430 |
+
========
|
| 1431 |
+
|
| 1432 |
+
sympy.core.function.nfloat
|
| 1433 |
+
|
| 1434 |
+
"""
|
| 1435 |
+
try:
|
| 1436 |
+
return sympify(as_int(expr))
|
| 1437 |
+
except (TypeError, ValueError):
|
| 1438 |
+
pass
|
| 1439 |
+
expr = sympify(expr).xreplace({
|
| 1440 |
+
Float('inf'): S.Infinity,
|
| 1441 |
+
Float('-inf'): S.NegativeInfinity,
|
| 1442 |
+
})
|
| 1443 |
+
if expr is S.Infinity or expr is S.NegativeInfinity:
|
| 1444 |
+
return expr
|
| 1445 |
+
if rational or expr.free_symbols:
|
| 1446 |
+
return _real_to_rational(expr, tolerance, rational_conversion)
|
| 1447 |
+
|
| 1448 |
+
# SymPy's default tolerance for Rationals is 15; other numbers may have
|
| 1449 |
+
# lower tolerances set, so use them to pick the largest tolerance if None
|
| 1450 |
+
# was given
|
| 1451 |
+
if tolerance is None:
|
| 1452 |
+
tolerance = 10**-min([15] +
|
| 1453 |
+
[mpmath.libmp.libmpf.prec_to_dps(n._prec)
|
| 1454 |
+
for n in expr.atoms(Float)])
|
| 1455 |
+
# XXX should prec be set independent of tolerance or should it be computed
|
| 1456 |
+
# from tolerance?
|
| 1457 |
+
prec = 30
|
| 1458 |
+
bprec = int(prec*3.33)
|
| 1459 |
+
|
| 1460 |
+
constants_dict = {}
|
| 1461 |
+
for constant in constants:
|
| 1462 |
+
constant = sympify(constant)
|
| 1463 |
+
v = constant.evalf(prec)
|
| 1464 |
+
if not v.is_Float:
|
| 1465 |
+
raise ValueError("constants must be real-valued")
|
| 1466 |
+
constants_dict[str(constant)] = v._to_mpmath(bprec)
|
| 1467 |
+
|
| 1468 |
+
exprval = expr.evalf(prec, chop=True)
|
| 1469 |
+
re, im = exprval.as_real_imag()
|
| 1470 |
+
|
| 1471 |
+
# safety check to make sure that this evaluated to a number
|
| 1472 |
+
if not (re.is_Number and im.is_Number):
|
| 1473 |
+
return expr
|
| 1474 |
+
|
| 1475 |
+
def nsimplify_real(x):
|
| 1476 |
+
orig = mpmath.mp.dps
|
| 1477 |
+
xv = x._to_mpmath(bprec)
|
| 1478 |
+
try:
|
| 1479 |
+
# We'll be happy with low precision if a simple fraction
|
| 1480 |
+
if not (tolerance or full):
|
| 1481 |
+
mpmath.mp.dps = 15
|
| 1482 |
+
rat = mpmath.pslq([xv, 1])
|
| 1483 |
+
if rat is not None:
|
| 1484 |
+
return Rational(-int(rat[1]), int(rat[0]))
|
| 1485 |
+
mpmath.mp.dps = prec
|
| 1486 |
+
newexpr = mpmath.identify(xv, constants=constants_dict,
|
| 1487 |
+
tol=tolerance, full=full)
|
| 1488 |
+
if not newexpr:
|
| 1489 |
+
raise ValueError
|
| 1490 |
+
if full:
|
| 1491 |
+
newexpr = newexpr[0]
|
| 1492 |
+
expr = sympify(newexpr)
|
| 1493 |
+
if x and not expr: # don't let x become 0
|
| 1494 |
+
raise ValueError
|
| 1495 |
+
if expr.is_finite is False and xv not in [mpmath.inf, mpmath.ninf]:
|
| 1496 |
+
raise ValueError
|
| 1497 |
+
return expr
|
| 1498 |
+
finally:
|
| 1499 |
+
# even though there are returns above, this is executed
|
| 1500 |
+
# before leaving
|
| 1501 |
+
mpmath.mp.dps = orig
|
| 1502 |
+
try:
|
| 1503 |
+
if re:
|
| 1504 |
+
re = nsimplify_real(re)
|
| 1505 |
+
if im:
|
| 1506 |
+
im = nsimplify_real(im)
|
| 1507 |
+
except ValueError:
|
| 1508 |
+
if rational is None:
|
| 1509 |
+
return _real_to_rational(expr, rational_conversion=rational_conversion)
|
| 1510 |
+
return expr
|
| 1511 |
+
|
| 1512 |
+
rv = re + im*S.ImaginaryUnit
|
| 1513 |
+
# if there was a change or rational is explicitly not wanted
|
| 1514 |
+
# return the value, else return the Rational representation
|
| 1515 |
+
if rv != expr or rational is False:
|
| 1516 |
+
return rv
|
| 1517 |
+
return _real_to_rational(expr, rational_conversion=rational_conversion)
|
| 1518 |
+
|
| 1519 |
+
|
| 1520 |
+
def _real_to_rational(expr, tolerance=None, rational_conversion='base10'):
|
| 1521 |
+
"""
|
| 1522 |
+
Replace all reals in expr with rationals.
|
| 1523 |
+
|
| 1524 |
+
Examples
|
| 1525 |
+
========
|
| 1526 |
+
|
| 1527 |
+
>>> from sympy.simplify.simplify import _real_to_rational
|
| 1528 |
+
>>> from sympy.abc import x
|
| 1529 |
+
|
| 1530 |
+
>>> _real_to_rational(.76 + .1*x**.5)
|
| 1531 |
+
sqrt(x)/10 + 19/25
|
| 1532 |
+
|
| 1533 |
+
If rational_conversion='base10', this uses the base-10 string. If
|
| 1534 |
+
rational_conversion='exact', the exact, base-2 representation is used.
|
| 1535 |
+
|
| 1536 |
+
>>> _real_to_rational(0.333333333333333, rational_conversion='exact')
|
| 1537 |
+
6004799503160655/18014398509481984
|
| 1538 |
+
>>> _real_to_rational(0.333333333333333)
|
| 1539 |
+
1/3
|
| 1540 |
+
|
| 1541 |
+
"""
|
| 1542 |
+
expr = _sympify(expr)
|
| 1543 |
+
inf = Float('inf')
|
| 1544 |
+
p = expr
|
| 1545 |
+
reps = {}
|
| 1546 |
+
reduce_num = None
|
| 1547 |
+
if tolerance is not None and tolerance < 1:
|
| 1548 |
+
reduce_num = ceiling(1/tolerance)
|
| 1549 |
+
for fl in p.atoms(Float):
|
| 1550 |
+
key = fl
|
| 1551 |
+
if reduce_num is not None:
|
| 1552 |
+
r = Rational(fl).limit_denominator(reduce_num)
|
| 1553 |
+
elif (tolerance is not None and tolerance >= 1 and
|
| 1554 |
+
fl.is_Integer is False):
|
| 1555 |
+
r = Rational(tolerance*round(fl/tolerance)
|
| 1556 |
+
).limit_denominator(int(tolerance))
|
| 1557 |
+
else:
|
| 1558 |
+
if rational_conversion == 'exact':
|
| 1559 |
+
r = Rational(fl)
|
| 1560 |
+
reps[key] = r
|
| 1561 |
+
continue
|
| 1562 |
+
elif rational_conversion != 'base10':
|
| 1563 |
+
raise ValueError("rational_conversion must be 'base10' or 'exact'")
|
| 1564 |
+
|
| 1565 |
+
r = nsimplify(fl, rational=False)
|
| 1566 |
+
# e.g. log(3).n() -> log(3) instead of a Rational
|
| 1567 |
+
if fl and not r:
|
| 1568 |
+
r = Rational(fl)
|
| 1569 |
+
elif not r.is_Rational:
|
| 1570 |
+
if fl in (inf, -inf):
|
| 1571 |
+
r = S.ComplexInfinity
|
| 1572 |
+
elif fl < 0:
|
| 1573 |
+
fl = -fl
|
| 1574 |
+
d = Pow(10, int(mpmath.log(fl)/mpmath.log(10)))
|
| 1575 |
+
r = -Rational(str(fl/d))*d
|
| 1576 |
+
elif fl > 0:
|
| 1577 |
+
d = Pow(10, int(mpmath.log(fl)/mpmath.log(10)))
|
| 1578 |
+
r = Rational(str(fl/d))*d
|
| 1579 |
+
else:
|
| 1580 |
+
r = S.Zero
|
| 1581 |
+
reps[key] = r
|
| 1582 |
+
return p.subs(reps, simultaneous=True)
|
| 1583 |
+
|
| 1584 |
+
|
| 1585 |
+
def clear_coefficients(expr, rhs=S.Zero):
|
| 1586 |
+
"""Return `p, r` where `p` is the expression obtained when Rational
|
| 1587 |
+
additive and multiplicative coefficients of `expr` have been stripped
|
| 1588 |
+
away in a naive fashion (i.e. without simplification). The operations
|
| 1589 |
+
needed to remove the coefficients will be applied to `rhs` and returned
|
| 1590 |
+
as `r`.
|
| 1591 |
+
|
| 1592 |
+
Examples
|
| 1593 |
+
========
|
| 1594 |
+
|
| 1595 |
+
>>> from sympy.simplify.simplify import clear_coefficients
|
| 1596 |
+
>>> from sympy.abc import x, y
|
| 1597 |
+
>>> from sympy import Dummy
|
| 1598 |
+
>>> expr = 4*y*(6*x + 3)
|
| 1599 |
+
>>> clear_coefficients(expr - 2)
|
| 1600 |
+
(y*(2*x + 1), 1/6)
|
| 1601 |
+
|
| 1602 |
+
When solving 2 or more expressions like `expr = a`,
|
| 1603 |
+
`expr = b`, etc..., it is advantageous to provide a Dummy symbol
|
| 1604 |
+
for `rhs` and simply replace it with `a`, `b`, etc... in `r`.
|
| 1605 |
+
|
| 1606 |
+
>>> rhs = Dummy('rhs')
|
| 1607 |
+
>>> clear_coefficients(expr, rhs)
|
| 1608 |
+
(y*(2*x + 1), _rhs/12)
|
| 1609 |
+
>>> _[1].subs(rhs, 2)
|
| 1610 |
+
1/6
|
| 1611 |
+
"""
|
| 1612 |
+
was = None
|
| 1613 |
+
free = expr.free_symbols
|
| 1614 |
+
if expr.is_Rational:
|
| 1615 |
+
return (S.Zero, rhs - expr)
|
| 1616 |
+
while expr and was != expr:
|
| 1617 |
+
was = expr
|
| 1618 |
+
m, expr = (
|
| 1619 |
+
expr.as_content_primitive()
|
| 1620 |
+
if free else
|
| 1621 |
+
factor_terms(expr).as_coeff_Mul(rational=True))
|
| 1622 |
+
rhs /= m
|
| 1623 |
+
c, expr = expr.as_coeff_Add(rational=True)
|
| 1624 |
+
rhs -= c
|
| 1625 |
+
expr = signsimp(expr, evaluate = False)
|
| 1626 |
+
if expr.could_extract_minus_sign():
|
| 1627 |
+
expr = -expr
|
| 1628 |
+
rhs = -rhs
|
| 1629 |
+
return expr, rhs
|
| 1630 |
+
|
| 1631 |
+
def nc_simplify(expr, deep=True):
|
| 1632 |
+
'''
|
| 1633 |
+
Simplify a non-commutative expression composed of multiplication
|
| 1634 |
+
and raising to a power by grouping repeated subterms into one power.
|
| 1635 |
+
Priority is given to simplifications that give the fewest number
|
| 1636 |
+
of arguments in the end (for example, in a*b*a*b*c*a*b*c simplifying
|
| 1637 |
+
to (a*b)**2*c*a*b*c gives 5 arguments while a*b*(a*b*c)**2 has 3).
|
| 1638 |
+
If ``expr`` is a sum of such terms, the sum of the simplified terms
|
| 1639 |
+
is returned.
|
| 1640 |
+
|
| 1641 |
+
Keyword argument ``deep`` controls whether or not subexpressions
|
| 1642 |
+
nested deeper inside the main expression are simplified. See examples
|
| 1643 |
+
below. Setting `deep` to `False` can save time on nested expressions
|
| 1644 |
+
that do not need simplifying on all levels.
|
| 1645 |
+
|
| 1646 |
+
Examples
|
| 1647 |
+
========
|
| 1648 |
+
|
| 1649 |
+
>>> from sympy import symbols
|
| 1650 |
+
>>> from sympy.simplify.simplify import nc_simplify
|
| 1651 |
+
>>> a, b, c = symbols("a b c", commutative=False)
|
| 1652 |
+
>>> nc_simplify(a*b*a*b*c*a*b*c)
|
| 1653 |
+
a*b*(a*b*c)**2
|
| 1654 |
+
>>> expr = a**2*b*a**4*b*a**4
|
| 1655 |
+
>>> nc_simplify(expr)
|
| 1656 |
+
a**2*(b*a**4)**2
|
| 1657 |
+
>>> nc_simplify(a*b*a*b*c**2*(a*b)**2*c**2)
|
| 1658 |
+
((a*b)**2*c**2)**2
|
| 1659 |
+
>>> nc_simplify(a*b*a*b + 2*a*c*a**2*c*a**2*c*a)
|
| 1660 |
+
(a*b)**2 + 2*(a*c*a)**3
|
| 1661 |
+
>>> nc_simplify(b**-1*a**-1*(a*b)**2)
|
| 1662 |
+
a*b
|
| 1663 |
+
>>> nc_simplify(a**-1*b**-1*c*a)
|
| 1664 |
+
(b*a)**(-1)*c*a
|
| 1665 |
+
>>> expr = (a*b*a*b)**2*a*c*a*c
|
| 1666 |
+
>>> nc_simplify(expr)
|
| 1667 |
+
(a*b)**4*(a*c)**2
|
| 1668 |
+
>>> nc_simplify(expr, deep=False)
|
| 1669 |
+
(a*b*a*b)**2*(a*c)**2
|
| 1670 |
+
|
| 1671 |
+
'''
|
| 1672 |
+
if isinstance(expr, MatrixExpr):
|
| 1673 |
+
expr = expr.doit(inv_expand=False)
|
| 1674 |
+
_Add, _Mul, _Pow, _Symbol = MatAdd, MatMul, MatPow, MatrixSymbol
|
| 1675 |
+
else:
|
| 1676 |
+
_Add, _Mul, _Pow, _Symbol = Add, Mul, Pow, Symbol
|
| 1677 |
+
|
| 1678 |
+
# =========== Auxiliary functions ========================
|
| 1679 |
+
def _overlaps(args):
|
| 1680 |
+
# Calculate a list of lists m such that m[i][j] contains the lengths
|
| 1681 |
+
# of all possible overlaps between args[:i+1] and args[i+1+j:].
|
| 1682 |
+
# An overlap is a suffix of the prefix that matches a prefix
|
| 1683 |
+
# of the suffix.
|
| 1684 |
+
# For example, let expr=c*a*b*a*b*a*b*a*b. Then m[3][0] contains
|
| 1685 |
+
# the lengths of overlaps of c*a*b*a*b with a*b*a*b. The overlaps
|
| 1686 |
+
# are a*b*a*b, a*b and the empty word so that m[3][0]=[4,2,0].
|
| 1687 |
+
# All overlaps rather than only the longest one are recorded
|
| 1688 |
+
# because this information helps calculate other overlap lengths.
|
| 1689 |
+
m = [[([1, 0] if a == args[0] else [0]) for a in args[1:]]]
|
| 1690 |
+
for i in range(1, len(args)):
|
| 1691 |
+
overlaps = []
|
| 1692 |
+
j = 0
|
| 1693 |
+
for j in range(len(args) - i - 1):
|
| 1694 |
+
overlap = []
|
| 1695 |
+
for v in m[i-1][j+1]:
|
| 1696 |
+
if j + i + 1 + v < len(args) and args[i] == args[j+i+1+v]:
|
| 1697 |
+
overlap.append(v + 1)
|
| 1698 |
+
overlap += [0]
|
| 1699 |
+
overlaps.append(overlap)
|
| 1700 |
+
m.append(overlaps)
|
| 1701 |
+
return m
|
| 1702 |
+
|
| 1703 |
+
def _reduce_inverses(_args):
|
| 1704 |
+
# replace consecutive negative powers by an inverse
|
| 1705 |
+
# of a product of positive powers, e.g. a**-1*b**-1*c
|
| 1706 |
+
# will simplify to (a*b)**-1*c;
|
| 1707 |
+
# return that new args list and the number of negative
|
| 1708 |
+
# powers in it (inv_tot)
|
| 1709 |
+
inv_tot = 0 # total number of inverses
|
| 1710 |
+
inverses = []
|
| 1711 |
+
args = []
|
| 1712 |
+
for arg in _args:
|
| 1713 |
+
if isinstance(arg, _Pow) and arg.args[1].is_extended_negative:
|
| 1714 |
+
inverses = [arg**-1] + inverses
|
| 1715 |
+
inv_tot += 1
|
| 1716 |
+
else:
|
| 1717 |
+
if len(inverses) == 1:
|
| 1718 |
+
args.append(inverses[0]**-1)
|
| 1719 |
+
elif len(inverses) > 1:
|
| 1720 |
+
args.append(_Pow(_Mul(*inverses), -1))
|
| 1721 |
+
inv_tot -= len(inverses) - 1
|
| 1722 |
+
inverses = []
|
| 1723 |
+
args.append(arg)
|
| 1724 |
+
if inverses:
|
| 1725 |
+
args.append(_Pow(_Mul(*inverses), -1))
|
| 1726 |
+
inv_tot -= len(inverses) - 1
|
| 1727 |
+
return inv_tot, tuple(args)
|
| 1728 |
+
|
| 1729 |
+
def get_score(s):
|
| 1730 |
+
# compute the number of arguments of s
|
| 1731 |
+
# (including in nested expressions) overall
|
| 1732 |
+
# but ignore exponents
|
| 1733 |
+
if isinstance(s, _Pow):
|
| 1734 |
+
return get_score(s.args[0])
|
| 1735 |
+
elif isinstance(s, (_Add, _Mul)):
|
| 1736 |
+
return sum(get_score(a) for a in s.args)
|
| 1737 |
+
return 1
|
| 1738 |
+
|
| 1739 |
+
def compare(s, alt_s):
|
| 1740 |
+
# compare two possible simplifications and return a
|
| 1741 |
+
# "better" one
|
| 1742 |
+
if s != alt_s and get_score(alt_s) < get_score(s):
|
| 1743 |
+
return alt_s
|
| 1744 |
+
return s
|
| 1745 |
+
# ========================================================
|
| 1746 |
+
|
| 1747 |
+
if not isinstance(expr, (_Add, _Mul, _Pow)) or expr.is_commutative:
|
| 1748 |
+
return expr
|
| 1749 |
+
args = expr.args[:]
|
| 1750 |
+
if isinstance(expr, _Pow):
|
| 1751 |
+
if deep:
|
| 1752 |
+
return _Pow(nc_simplify(args[0]), args[1]).doit()
|
| 1753 |
+
else:
|
| 1754 |
+
return expr
|
| 1755 |
+
elif isinstance(expr, _Add):
|
| 1756 |
+
return _Add(*[nc_simplify(a, deep=deep) for a in args]).doit()
|
| 1757 |
+
else:
|
| 1758 |
+
# get the non-commutative part
|
| 1759 |
+
c_args, args = expr.args_cnc()
|
| 1760 |
+
com_coeff = Mul(*c_args)
|
| 1761 |
+
if not equal_valued(com_coeff, 1):
|
| 1762 |
+
return com_coeff*nc_simplify(expr/com_coeff, deep=deep)
|
| 1763 |
+
|
| 1764 |
+
inv_tot, args = _reduce_inverses(args)
|
| 1765 |
+
# if most arguments are negative, work with the inverse
|
| 1766 |
+
# of the expression, e.g. a**-1*b*a**-1*c**-1 will become
|
| 1767 |
+
# (c*a*b**-1*a)**-1 at the end so can work with c*a*b**-1*a
|
| 1768 |
+
invert = False
|
| 1769 |
+
if inv_tot > len(args)/2:
|
| 1770 |
+
invert = True
|
| 1771 |
+
args = [a**-1 for a in args[::-1]]
|
| 1772 |
+
|
| 1773 |
+
if deep:
|
| 1774 |
+
args = tuple(nc_simplify(a) for a in args)
|
| 1775 |
+
|
| 1776 |
+
m = _overlaps(args)
|
| 1777 |
+
|
| 1778 |
+
# simps will be {subterm: end} where `end` is the ending
|
| 1779 |
+
# index of a sequence of repetitions of subterm;
|
| 1780 |
+
# this is for not wasting time with subterms that are part
|
| 1781 |
+
# of longer, already considered sequences
|
| 1782 |
+
simps = {}
|
| 1783 |
+
|
| 1784 |
+
post = 1
|
| 1785 |
+
pre = 1
|
| 1786 |
+
|
| 1787 |
+
# the simplification coefficient is the number of
|
| 1788 |
+
# arguments by which contracting a given sequence
|
| 1789 |
+
# would reduce the word; e.g. in a*b*a*b*c*a*b*c,
|
| 1790 |
+
# contracting a*b*a*b to (a*b)**2 removes 3 arguments
|
| 1791 |
+
# while a*b*c*a*b*c to (a*b*c)**2 removes 6. It's
|
| 1792 |
+
# better to contract the latter so simplification
|
| 1793 |
+
# with a maximum simplification coefficient will be chosen
|
| 1794 |
+
max_simp_coeff = 0
|
| 1795 |
+
simp = None # information about future simplification
|
| 1796 |
+
|
| 1797 |
+
for i in range(1, len(args)):
|
| 1798 |
+
simp_coeff = 0
|
| 1799 |
+
l = 0 # length of a subterm
|
| 1800 |
+
p = 0 # the power of a subterm
|
| 1801 |
+
if i < len(args) - 1:
|
| 1802 |
+
rep = m[i][0]
|
| 1803 |
+
start = i # starting index of the repeated sequence
|
| 1804 |
+
end = i+1 # ending index of the repeated sequence
|
| 1805 |
+
if i == len(args)-1 or rep == [0]:
|
| 1806 |
+
# no subterm is repeated at this stage, at least as
|
| 1807 |
+
# far as the arguments are concerned - there may be
|
| 1808 |
+
# a repetition if powers are taken into account
|
| 1809 |
+
if (isinstance(args[i], _Pow) and
|
| 1810 |
+
not isinstance(args[i].args[0], _Symbol)):
|
| 1811 |
+
subterm = args[i].args[0].args
|
| 1812 |
+
l = len(subterm)
|
| 1813 |
+
if args[i-l:i] == subterm:
|
| 1814 |
+
# e.g. a*b in a*b*(a*b)**2 is not repeated
|
| 1815 |
+
# in args (= [a, b, (a*b)**2]) but it
|
| 1816 |
+
# can be matched here
|
| 1817 |
+
p += 1
|
| 1818 |
+
start -= l
|
| 1819 |
+
if args[i+1:i+1+l] == subterm:
|
| 1820 |
+
# e.g. a*b in (a*b)**2*a*b
|
| 1821 |
+
p += 1
|
| 1822 |
+
end += l
|
| 1823 |
+
if p:
|
| 1824 |
+
p += args[i].args[1]
|
| 1825 |
+
else:
|
| 1826 |
+
continue
|
| 1827 |
+
else:
|
| 1828 |
+
l = rep[0] # length of the longest repeated subterm at this point
|
| 1829 |
+
start -= l - 1
|
| 1830 |
+
subterm = args[start:end]
|
| 1831 |
+
p = 2
|
| 1832 |
+
end += l
|
| 1833 |
+
|
| 1834 |
+
if subterm in simps and simps[subterm] >= start:
|
| 1835 |
+
# the subterm is part of a sequence that
|
| 1836 |
+
# has already been considered
|
| 1837 |
+
continue
|
| 1838 |
+
|
| 1839 |
+
# count how many times it's repeated
|
| 1840 |
+
while end < len(args):
|
| 1841 |
+
if l in m[end-1][0]:
|
| 1842 |
+
p += 1
|
| 1843 |
+
end += l
|
| 1844 |
+
elif isinstance(args[end], _Pow) and args[end].args[0].args == subterm:
|
| 1845 |
+
# for cases like a*b*a*b*(a*b)**2*a*b
|
| 1846 |
+
p += args[end].args[1]
|
| 1847 |
+
end += 1
|
| 1848 |
+
else:
|
| 1849 |
+
break
|
| 1850 |
+
|
| 1851 |
+
# see if another match can be made, e.g.
|
| 1852 |
+
# for b*a**2 in b*a**2*b*a**3 or a*b in
|
| 1853 |
+
# a**2*b*a*b
|
| 1854 |
+
|
| 1855 |
+
pre_exp = 0
|
| 1856 |
+
pre_arg = 1
|
| 1857 |
+
if start - l >= 0 and args[start-l+1:start] == subterm[1:]:
|
| 1858 |
+
if isinstance(subterm[0], _Pow):
|
| 1859 |
+
pre_arg = subterm[0].args[0]
|
| 1860 |
+
exp = subterm[0].args[1]
|
| 1861 |
+
else:
|
| 1862 |
+
pre_arg = subterm[0]
|
| 1863 |
+
exp = 1
|
| 1864 |
+
if isinstance(args[start-l], _Pow) and args[start-l].args[0] == pre_arg:
|
| 1865 |
+
pre_exp = args[start-l].args[1] - exp
|
| 1866 |
+
start -= l
|
| 1867 |
+
p += 1
|
| 1868 |
+
elif args[start-l] == pre_arg:
|
| 1869 |
+
pre_exp = 1 - exp
|
| 1870 |
+
start -= l
|
| 1871 |
+
p += 1
|
| 1872 |
+
|
| 1873 |
+
post_exp = 0
|
| 1874 |
+
post_arg = 1
|
| 1875 |
+
if end + l - 1 < len(args) and args[end:end+l-1] == subterm[:-1]:
|
| 1876 |
+
if isinstance(subterm[-1], _Pow):
|
| 1877 |
+
post_arg = subterm[-1].args[0]
|
| 1878 |
+
exp = subterm[-1].args[1]
|
| 1879 |
+
else:
|
| 1880 |
+
post_arg = subterm[-1]
|
| 1881 |
+
exp = 1
|
| 1882 |
+
if isinstance(args[end+l-1], _Pow) and args[end+l-1].args[0] == post_arg:
|
| 1883 |
+
post_exp = args[end+l-1].args[1] - exp
|
| 1884 |
+
end += l
|
| 1885 |
+
p += 1
|
| 1886 |
+
elif args[end+l-1] == post_arg:
|
| 1887 |
+
post_exp = 1 - exp
|
| 1888 |
+
end += l
|
| 1889 |
+
p += 1
|
| 1890 |
+
|
| 1891 |
+
# Consider a*b*a**2*b*a**2*b*a:
|
| 1892 |
+
# b*a**2 is explicitly repeated, but note
|
| 1893 |
+
# that in this case a*b*a is also repeated
|
| 1894 |
+
# so there are two possible simplifications:
|
| 1895 |
+
# a*(b*a**2)**3*a**-1 or (a*b*a)**3
|
| 1896 |
+
# The latter is obviously simpler.
|
| 1897 |
+
# But in a*b*a**2*b**2*a**2 the simplifications are
|
| 1898 |
+
# a*(b*a**2)**2 and (a*b*a)**3*a in which case
|
| 1899 |
+
# it's better to stick with the shorter subterm
|
| 1900 |
+
if post_exp and exp % 2 == 0 and start > 0:
|
| 1901 |
+
exp = exp/2
|
| 1902 |
+
_pre_exp = 1
|
| 1903 |
+
_post_exp = 1
|
| 1904 |
+
if isinstance(args[start-1], _Pow) and args[start-1].args[0] == post_arg:
|
| 1905 |
+
_post_exp = post_exp + exp
|
| 1906 |
+
_pre_exp = args[start-1].args[1] - exp
|
| 1907 |
+
elif args[start-1] == post_arg:
|
| 1908 |
+
_post_exp = post_exp + exp
|
| 1909 |
+
_pre_exp = 1 - exp
|
| 1910 |
+
if _pre_exp == 0 or _post_exp == 0:
|
| 1911 |
+
if not pre_exp:
|
| 1912 |
+
start -= 1
|
| 1913 |
+
post_exp = _post_exp
|
| 1914 |
+
pre_exp = _pre_exp
|
| 1915 |
+
pre_arg = post_arg
|
| 1916 |
+
subterm = (post_arg**exp,) + subterm[:-1] + (post_arg**exp,)
|
| 1917 |
+
|
| 1918 |
+
simp_coeff += end-start
|
| 1919 |
+
|
| 1920 |
+
if post_exp:
|
| 1921 |
+
simp_coeff -= 1
|
| 1922 |
+
if pre_exp:
|
| 1923 |
+
simp_coeff -= 1
|
| 1924 |
+
|
| 1925 |
+
simps[subterm] = end
|
| 1926 |
+
|
| 1927 |
+
if simp_coeff > max_simp_coeff:
|
| 1928 |
+
max_simp_coeff = simp_coeff
|
| 1929 |
+
simp = (start, _Mul(*subterm), p, end, l)
|
| 1930 |
+
pre = pre_arg**pre_exp
|
| 1931 |
+
post = post_arg**post_exp
|
| 1932 |
+
|
| 1933 |
+
if simp:
|
| 1934 |
+
subterm = _Pow(nc_simplify(simp[1], deep=deep), simp[2])
|
| 1935 |
+
pre = nc_simplify(_Mul(*args[:simp[0]])*pre, deep=deep)
|
| 1936 |
+
post = post*nc_simplify(_Mul(*args[simp[3]:]), deep=deep)
|
| 1937 |
+
simp = pre*subterm*post
|
| 1938 |
+
if pre != 1 or post != 1:
|
| 1939 |
+
# new simplifications may be possible but no need
|
| 1940 |
+
# to recurse over arguments
|
| 1941 |
+
simp = nc_simplify(simp, deep=False)
|
| 1942 |
+
else:
|
| 1943 |
+
simp = _Mul(*args)
|
| 1944 |
+
|
| 1945 |
+
if invert:
|
| 1946 |
+
simp = _Pow(simp, -1)
|
| 1947 |
+
|
| 1948 |
+
# see if factor_nc(expr) is simplified better
|
| 1949 |
+
if not isinstance(expr, MatrixExpr):
|
| 1950 |
+
f_expr = factor_nc(expr)
|
| 1951 |
+
if f_expr != expr:
|
| 1952 |
+
alt_simp = nc_simplify(f_expr, deep=deep)
|
| 1953 |
+
simp = compare(simp, alt_simp)
|
| 1954 |
+
else:
|
| 1955 |
+
simp = simp.doit(inv_expand=False)
|
| 1956 |
+
return simp
|
| 1957 |
+
|
| 1958 |
+
|
| 1959 |
+
def dotprodsimp(expr, withsimp=False):
|
| 1960 |
+
"""Simplification for a sum of products targeted at the kind of blowup that
|
| 1961 |
+
occurs during summation of products. Intended to reduce expression blowup
|
| 1962 |
+
during matrix multiplication or other similar operations. Only works with
|
| 1963 |
+
algebraic expressions and does not recurse into non.
|
| 1964 |
+
|
| 1965 |
+
Parameters
|
| 1966 |
+
==========
|
| 1967 |
+
|
| 1968 |
+
withsimp : bool, optional
|
| 1969 |
+
Specifies whether a flag should be returned along with the expression
|
| 1970 |
+
to indicate roughly whether simplification was successful. It is used
|
| 1971 |
+
in ``MatrixArithmetic._eval_pow_by_recursion`` to avoid attempting to
|
| 1972 |
+
simplify an expression repetitively which does not simplify.
|
| 1973 |
+
"""
|
| 1974 |
+
|
| 1975 |
+
def count_ops_alg(expr):
|
| 1976 |
+
"""Optimized count algebraic operations with no recursion into
|
| 1977 |
+
non-algebraic args that ``core.function.count_ops`` does. Also returns
|
| 1978 |
+
whether rational functions may be present according to negative
|
| 1979 |
+
exponents of powers or non-number fractions.
|
| 1980 |
+
|
| 1981 |
+
Returns
|
| 1982 |
+
=======
|
| 1983 |
+
|
| 1984 |
+
ops, ratfunc : int, bool
|
| 1985 |
+
``ops`` is the number of algebraic operations starting at the top
|
| 1986 |
+
level expression (not recursing into non-alg children). ``ratfunc``
|
| 1987 |
+
specifies whether the expression MAY contain rational functions
|
| 1988 |
+
which ``cancel`` MIGHT optimize.
|
| 1989 |
+
"""
|
| 1990 |
+
|
| 1991 |
+
ops = 0
|
| 1992 |
+
args = [expr]
|
| 1993 |
+
ratfunc = False
|
| 1994 |
+
|
| 1995 |
+
while args:
|
| 1996 |
+
a = args.pop()
|
| 1997 |
+
|
| 1998 |
+
if not isinstance(a, Basic):
|
| 1999 |
+
continue
|
| 2000 |
+
|
| 2001 |
+
if a.is_Rational:
|
| 2002 |
+
if a is not S.One: # -1/3 = NEG + DIV
|
| 2003 |
+
ops += bool (a.p < 0) + bool (a.q != 1)
|
| 2004 |
+
|
| 2005 |
+
elif a.is_Mul:
|
| 2006 |
+
if a.could_extract_minus_sign():
|
| 2007 |
+
ops += 1
|
| 2008 |
+
if a.args[0] is S.NegativeOne:
|
| 2009 |
+
a = a.as_two_terms()[1]
|
| 2010 |
+
else:
|
| 2011 |
+
a = -a
|
| 2012 |
+
|
| 2013 |
+
n, d = fraction(a)
|
| 2014 |
+
|
| 2015 |
+
if n.is_Integer:
|
| 2016 |
+
ops += 1 + bool (n < 0)
|
| 2017 |
+
args.append(d) # won't be -Mul but could be Add
|
| 2018 |
+
|
| 2019 |
+
elif d is not S.One:
|
| 2020 |
+
if not d.is_Integer:
|
| 2021 |
+
args.append(d)
|
| 2022 |
+
ratfunc=True
|
| 2023 |
+
|
| 2024 |
+
ops += 1
|
| 2025 |
+
args.append(n) # could be -Mul
|
| 2026 |
+
|
| 2027 |
+
else:
|
| 2028 |
+
ops += len(a.args) - 1
|
| 2029 |
+
args.extend(a.args)
|
| 2030 |
+
|
| 2031 |
+
elif a.is_Add:
|
| 2032 |
+
laargs = len(a.args)
|
| 2033 |
+
negs = 0
|
| 2034 |
+
|
| 2035 |
+
for ai in a.args:
|
| 2036 |
+
if ai.could_extract_minus_sign():
|
| 2037 |
+
negs += 1
|
| 2038 |
+
ai = -ai
|
| 2039 |
+
args.append(ai)
|
| 2040 |
+
|
| 2041 |
+
ops += laargs - (negs != laargs) # -x - y = NEG + SUB
|
| 2042 |
+
|
| 2043 |
+
elif a.is_Pow:
|
| 2044 |
+
ops += 1
|
| 2045 |
+
args.append(a.base)
|
| 2046 |
+
|
| 2047 |
+
if not ratfunc:
|
| 2048 |
+
ratfunc = a.exp.is_negative is not False
|
| 2049 |
+
|
| 2050 |
+
return ops, ratfunc
|
| 2051 |
+
|
| 2052 |
+
def nonalg_subs_dummies(expr, dummies):
|
| 2053 |
+
"""Substitute dummy variables for non-algebraic expressions to avoid
|
| 2054 |
+
evaluation of non-algebraic terms that ``polys.polytools.cancel`` does.
|
| 2055 |
+
"""
|
| 2056 |
+
|
| 2057 |
+
if not expr.args:
|
| 2058 |
+
return expr
|
| 2059 |
+
|
| 2060 |
+
if expr.is_Add or expr.is_Mul or expr.is_Pow:
|
| 2061 |
+
args = None
|
| 2062 |
+
|
| 2063 |
+
for i, a in enumerate(expr.args):
|
| 2064 |
+
c = nonalg_subs_dummies(a, dummies)
|
| 2065 |
+
|
| 2066 |
+
if c is a:
|
| 2067 |
+
continue
|
| 2068 |
+
|
| 2069 |
+
if args is None:
|
| 2070 |
+
args = list(expr.args)
|
| 2071 |
+
|
| 2072 |
+
args[i] = c
|
| 2073 |
+
|
| 2074 |
+
if args is None:
|
| 2075 |
+
return expr
|
| 2076 |
+
|
| 2077 |
+
return expr.func(*args)
|
| 2078 |
+
|
| 2079 |
+
return dummies.setdefault(expr, Dummy())
|
| 2080 |
+
|
| 2081 |
+
simplified = False # doesn't really mean simplified, rather "can simplify again"
|
| 2082 |
+
|
| 2083 |
+
if isinstance(expr, Basic) and (expr.is_Add or expr.is_Mul or expr.is_Pow):
|
| 2084 |
+
expr2 = expr.expand(deep=True, modulus=None, power_base=False,
|
| 2085 |
+
power_exp=False, mul=True, log=False, multinomial=True, basic=False)
|
| 2086 |
+
|
| 2087 |
+
if expr2 != expr:
|
| 2088 |
+
expr = expr2
|
| 2089 |
+
simplified = True
|
| 2090 |
+
|
| 2091 |
+
exprops, ratfunc = count_ops_alg(expr)
|
| 2092 |
+
|
| 2093 |
+
if exprops >= 6: # empirically tested cutoff for expensive simplification
|
| 2094 |
+
if ratfunc:
|
| 2095 |
+
dummies = {}
|
| 2096 |
+
expr2 = nonalg_subs_dummies(expr, dummies)
|
| 2097 |
+
|
| 2098 |
+
if expr2 is expr or count_ops_alg(expr2)[0] >= 6: # check again after substitution
|
| 2099 |
+
expr3 = cancel(expr2)
|
| 2100 |
+
|
| 2101 |
+
if expr3 != expr2:
|
| 2102 |
+
expr = expr3.subs([(d, e) for e, d in dummies.items()])
|
| 2103 |
+
simplified = True
|
| 2104 |
+
|
| 2105 |
+
# very special case: x/(x-1) - 1/(x-1) -> 1
|
| 2106 |
+
elif (exprops == 5 and expr.is_Add and expr.args [0].is_Mul and
|
| 2107 |
+
expr.args [1].is_Mul and expr.args [0].args [-1].is_Pow and
|
| 2108 |
+
expr.args [1].args [-1].is_Pow and
|
| 2109 |
+
expr.args [0].args [-1].exp is S.NegativeOne and
|
| 2110 |
+
expr.args [1].args [-1].exp is S.NegativeOne):
|
| 2111 |
+
|
| 2112 |
+
expr2 = together (expr)
|
| 2113 |
+
expr2ops = count_ops_alg(expr2)[0]
|
| 2114 |
+
|
| 2115 |
+
if expr2ops < exprops:
|
| 2116 |
+
expr = expr2
|
| 2117 |
+
simplified = True
|
| 2118 |
+
|
| 2119 |
+
else:
|
| 2120 |
+
simplified = True
|
| 2121 |
+
|
| 2122 |
+
return (expr, simplified) if withsimp else expr
|
| 2123 |
+
|
| 2124 |
+
|
| 2125 |
+
bottom_up = deprecated(
|
| 2126 |
+
"""
|
| 2127 |
+
Using bottom_up from the sympy.simplify.simplify submodule is
|
| 2128 |
+
deprecated.
|
| 2129 |
+
|
| 2130 |
+
Instead, use bottom_up from the top-level sympy namespace, like
|
| 2131 |
+
|
| 2132 |
+
sympy.bottom_up
|
| 2133 |
+
""",
|
| 2134 |
+
deprecated_since_version="1.10",
|
| 2135 |
+
active_deprecations_target="deprecated-traversal-functions-moved",
|
| 2136 |
+
)(_bottom_up)
|
| 2137 |
+
|
| 2138 |
+
|
| 2139 |
+
# XXX: This function really should either be private API or exported in the
|
| 2140 |
+
# top-level sympy/__init__.py
|
| 2141 |
+
walk = deprecated(
|
| 2142 |
+
"""
|
| 2143 |
+
Using walk from the sympy.simplify.simplify submodule is
|
| 2144 |
+
deprecated.
|
| 2145 |
+
|
| 2146 |
+
Instead, use walk from sympy.core.traversal.walk
|
| 2147 |
+
""",
|
| 2148 |
+
deprecated_since_version="1.10",
|
| 2149 |
+
active_deprecations_target="deprecated-traversal-functions-moved",
|
| 2150 |
+
)(_walk)
|
lib/python3.12/site-packages/sympy/simplify/sqrtdenest.py
ADDED
|
@@ -0,0 +1,678 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core import Add, Expr, Mul, S, sympify
|
| 2 |
+
from sympy.core.function import _mexpand, count_ops, expand_mul
|
| 3 |
+
from sympy.core.sorting import default_sort_key
|
| 4 |
+
from sympy.core.symbol import Dummy
|
| 5 |
+
from sympy.functions import root, sign, sqrt
|
| 6 |
+
from sympy.polys import Poly, PolynomialError
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def is_sqrt(expr):
|
| 10 |
+
"""Return True if expr is a sqrt, otherwise False."""
|
| 11 |
+
|
| 12 |
+
return expr.is_Pow and expr.exp.is_Rational and abs(expr.exp) is S.Half
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def sqrt_depth(p) -> int:
|
| 16 |
+
"""Return the maximum depth of any square root argument of p.
|
| 17 |
+
|
| 18 |
+
>>> from sympy.functions.elementary.miscellaneous import sqrt
|
| 19 |
+
>>> from sympy.simplify.sqrtdenest import sqrt_depth
|
| 20 |
+
|
| 21 |
+
Neither of these square roots contains any other square roots
|
| 22 |
+
so the depth is 1:
|
| 23 |
+
|
| 24 |
+
>>> sqrt_depth(1 + sqrt(2)*(1 + sqrt(3)))
|
| 25 |
+
1
|
| 26 |
+
|
| 27 |
+
The sqrt(3) is contained within a square root so the depth is
|
| 28 |
+
2:
|
| 29 |
+
|
| 30 |
+
>>> sqrt_depth(1 + sqrt(2)*sqrt(1 + sqrt(3)))
|
| 31 |
+
2
|
| 32 |
+
"""
|
| 33 |
+
if p is S.ImaginaryUnit:
|
| 34 |
+
return 1
|
| 35 |
+
if p.is_Atom:
|
| 36 |
+
return 0
|
| 37 |
+
if p.is_Add or p.is_Mul:
|
| 38 |
+
return max(sqrt_depth(x) for x in p.args)
|
| 39 |
+
if is_sqrt(p):
|
| 40 |
+
return sqrt_depth(p.base) + 1
|
| 41 |
+
return 0
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def is_algebraic(p):
|
| 45 |
+
"""Return True if p is comprised of only Rationals or square roots
|
| 46 |
+
of Rationals and algebraic operations.
|
| 47 |
+
|
| 48 |
+
Examples
|
| 49 |
+
========
|
| 50 |
+
|
| 51 |
+
>>> from sympy.functions.elementary.miscellaneous import sqrt
|
| 52 |
+
>>> from sympy.simplify.sqrtdenest import is_algebraic
|
| 53 |
+
>>> from sympy import cos
|
| 54 |
+
>>> is_algebraic(sqrt(2)*(3/(sqrt(7) + sqrt(5)*sqrt(2))))
|
| 55 |
+
True
|
| 56 |
+
>>> is_algebraic(sqrt(2)*(3/(sqrt(7) + sqrt(5)*cos(2))))
|
| 57 |
+
False
|
| 58 |
+
"""
|
| 59 |
+
|
| 60 |
+
if p.is_Rational:
|
| 61 |
+
return True
|
| 62 |
+
elif p.is_Atom:
|
| 63 |
+
return False
|
| 64 |
+
elif is_sqrt(p) or p.is_Pow and p.exp.is_Integer:
|
| 65 |
+
return is_algebraic(p.base)
|
| 66 |
+
elif p.is_Add or p.is_Mul:
|
| 67 |
+
return all(is_algebraic(x) for x in p.args)
|
| 68 |
+
else:
|
| 69 |
+
return False
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _subsets(n):
|
| 73 |
+
"""
|
| 74 |
+
Returns all possible subsets of the set (0, 1, ..., n-1) except the
|
| 75 |
+
empty set, listed in reversed lexicographical order according to binary
|
| 76 |
+
representation, so that the case of the fourth root is treated last.
|
| 77 |
+
|
| 78 |
+
Examples
|
| 79 |
+
========
|
| 80 |
+
|
| 81 |
+
>>> from sympy.simplify.sqrtdenest import _subsets
|
| 82 |
+
>>> _subsets(2)
|
| 83 |
+
[[1, 0], [0, 1], [1, 1]]
|
| 84 |
+
|
| 85 |
+
"""
|
| 86 |
+
if n == 1:
|
| 87 |
+
a = [[1]]
|
| 88 |
+
elif n == 2:
|
| 89 |
+
a = [[1, 0], [0, 1], [1, 1]]
|
| 90 |
+
elif n == 3:
|
| 91 |
+
a = [[1, 0, 0], [0, 1, 0], [1, 1, 0],
|
| 92 |
+
[0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1]]
|
| 93 |
+
else:
|
| 94 |
+
b = _subsets(n - 1)
|
| 95 |
+
a0 = [x + [0] for x in b]
|
| 96 |
+
a1 = [x + [1] for x in b]
|
| 97 |
+
a = a0 + [[0]*(n - 1) + [1]] + a1
|
| 98 |
+
return a
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def sqrtdenest(expr, max_iter=3):
|
| 102 |
+
"""Denests sqrts in an expression that contain other square roots
|
| 103 |
+
if possible, otherwise returns the expr unchanged. This is based on the
|
| 104 |
+
algorithms of [1].
|
| 105 |
+
|
| 106 |
+
Examples
|
| 107 |
+
========
|
| 108 |
+
|
| 109 |
+
>>> from sympy.simplify.sqrtdenest import sqrtdenest
|
| 110 |
+
>>> from sympy import sqrt
|
| 111 |
+
>>> sqrtdenest(sqrt(5 + 2 * sqrt(6)))
|
| 112 |
+
sqrt(2) + sqrt(3)
|
| 113 |
+
|
| 114 |
+
See Also
|
| 115 |
+
========
|
| 116 |
+
|
| 117 |
+
sympy.solvers.solvers.unrad
|
| 118 |
+
|
| 119 |
+
References
|
| 120 |
+
==========
|
| 121 |
+
|
| 122 |
+
.. [1] https://web.archive.org/web/20210806201615/https://researcher.watson.ibm.com/researcher/files/us-fagin/symb85.pdf
|
| 123 |
+
|
| 124 |
+
.. [2] D. J. Jeffrey and A. D. Rich, 'Symplifying Square Roots of Square Roots
|
| 125 |
+
by Denesting' (available at https://www.cybertester.com/data/denest.pdf)
|
| 126 |
+
|
| 127 |
+
"""
|
| 128 |
+
expr = expand_mul(expr)
|
| 129 |
+
for i in range(max_iter):
|
| 130 |
+
z = _sqrtdenest0(expr)
|
| 131 |
+
if expr == z:
|
| 132 |
+
return expr
|
| 133 |
+
expr = z
|
| 134 |
+
return expr
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def _sqrt_match(p):
|
| 138 |
+
"""Return [a, b, r] for p.match(a + b*sqrt(r)) where, in addition to
|
| 139 |
+
matching, sqrt(r) also has then maximal sqrt_depth among addends of p.
|
| 140 |
+
|
| 141 |
+
Examples
|
| 142 |
+
========
|
| 143 |
+
|
| 144 |
+
>>> from sympy.functions.elementary.miscellaneous import sqrt
|
| 145 |
+
>>> from sympy.simplify.sqrtdenest import _sqrt_match
|
| 146 |
+
>>> _sqrt_match(1 + sqrt(2) + sqrt(2)*sqrt(3) + 2*sqrt(1+sqrt(5)))
|
| 147 |
+
[1 + sqrt(2) + sqrt(6), 2, 1 + sqrt(5)]
|
| 148 |
+
"""
|
| 149 |
+
from sympy.simplify.radsimp import split_surds
|
| 150 |
+
|
| 151 |
+
p = _mexpand(p)
|
| 152 |
+
if p.is_Number:
|
| 153 |
+
res = (p, S.Zero, S.Zero)
|
| 154 |
+
elif p.is_Add:
|
| 155 |
+
pargs = sorted(p.args, key=default_sort_key)
|
| 156 |
+
sqargs = [x**2 for x in pargs]
|
| 157 |
+
if all(sq.is_Rational and sq.is_positive for sq in sqargs):
|
| 158 |
+
r, b, a = split_surds(p)
|
| 159 |
+
res = a, b, r
|
| 160 |
+
return list(res)
|
| 161 |
+
# to make the process canonical, the argument is included in the tuple
|
| 162 |
+
# so when the max is selected, it will be the largest arg having a
|
| 163 |
+
# given depth
|
| 164 |
+
v = [(sqrt_depth(x), x, i) for i, x in enumerate(pargs)]
|
| 165 |
+
nmax = max(v, key=default_sort_key)
|
| 166 |
+
if nmax[0] == 0:
|
| 167 |
+
res = []
|
| 168 |
+
else:
|
| 169 |
+
# select r
|
| 170 |
+
depth, _, i = nmax
|
| 171 |
+
r = pargs.pop(i)
|
| 172 |
+
v.pop(i)
|
| 173 |
+
b = S.One
|
| 174 |
+
if r.is_Mul:
|
| 175 |
+
bv = []
|
| 176 |
+
rv = []
|
| 177 |
+
for x in r.args:
|
| 178 |
+
if sqrt_depth(x) < depth:
|
| 179 |
+
bv.append(x)
|
| 180 |
+
else:
|
| 181 |
+
rv.append(x)
|
| 182 |
+
b = Mul._from_args(bv)
|
| 183 |
+
r = Mul._from_args(rv)
|
| 184 |
+
# collect terms comtaining r
|
| 185 |
+
a1 = []
|
| 186 |
+
b1 = [b]
|
| 187 |
+
for x in v:
|
| 188 |
+
if x[0] < depth:
|
| 189 |
+
a1.append(x[1])
|
| 190 |
+
else:
|
| 191 |
+
x1 = x[1]
|
| 192 |
+
if x1 == r:
|
| 193 |
+
b1.append(1)
|
| 194 |
+
else:
|
| 195 |
+
if x1.is_Mul:
|
| 196 |
+
x1args = list(x1.args)
|
| 197 |
+
if r in x1args:
|
| 198 |
+
x1args.remove(r)
|
| 199 |
+
b1.append(Mul(*x1args))
|
| 200 |
+
else:
|
| 201 |
+
a1.append(x[1])
|
| 202 |
+
else:
|
| 203 |
+
a1.append(x[1])
|
| 204 |
+
a = Add(*a1)
|
| 205 |
+
b = Add(*b1)
|
| 206 |
+
res = (a, b, r**2)
|
| 207 |
+
else:
|
| 208 |
+
b, r = p.as_coeff_Mul()
|
| 209 |
+
if is_sqrt(r):
|
| 210 |
+
res = (S.Zero, b, r**2)
|
| 211 |
+
else:
|
| 212 |
+
res = []
|
| 213 |
+
return list(res)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
class SqrtdenestStopIteration(StopIteration):
|
| 217 |
+
pass
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def _sqrtdenest0(expr):
|
| 221 |
+
"""Returns expr after denesting its arguments."""
|
| 222 |
+
|
| 223 |
+
if is_sqrt(expr):
|
| 224 |
+
n, d = expr.as_numer_denom()
|
| 225 |
+
if d is S.One: # n is a square root
|
| 226 |
+
if n.base.is_Add:
|
| 227 |
+
args = sorted(n.base.args, key=default_sort_key)
|
| 228 |
+
if len(args) > 2 and all((x**2).is_Integer for x in args):
|
| 229 |
+
try:
|
| 230 |
+
return _sqrtdenest_rec(n)
|
| 231 |
+
except SqrtdenestStopIteration:
|
| 232 |
+
pass
|
| 233 |
+
expr = sqrt(_mexpand(Add(*[_sqrtdenest0(x) for x in args])))
|
| 234 |
+
return _sqrtdenest1(expr)
|
| 235 |
+
else:
|
| 236 |
+
n, d = [_sqrtdenest0(i) for i in (n, d)]
|
| 237 |
+
return n/d
|
| 238 |
+
|
| 239 |
+
if isinstance(expr, Add):
|
| 240 |
+
cs = []
|
| 241 |
+
args = []
|
| 242 |
+
for arg in expr.args:
|
| 243 |
+
c, a = arg.as_coeff_Mul()
|
| 244 |
+
cs.append(c)
|
| 245 |
+
args.append(a)
|
| 246 |
+
|
| 247 |
+
if all(c.is_Rational for c in cs) and all(is_sqrt(arg) for arg in args):
|
| 248 |
+
return _sqrt_ratcomb(cs, args)
|
| 249 |
+
|
| 250 |
+
if isinstance(expr, Expr):
|
| 251 |
+
args = expr.args
|
| 252 |
+
if args:
|
| 253 |
+
return expr.func(*[_sqrtdenest0(a) for a in args])
|
| 254 |
+
return expr
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def _sqrtdenest_rec(expr):
|
| 258 |
+
"""Helper that denests the square root of three or more surds.
|
| 259 |
+
|
| 260 |
+
Explanation
|
| 261 |
+
===========
|
| 262 |
+
|
| 263 |
+
It returns the denested expression; if it cannot be denested it
|
| 264 |
+
throws SqrtdenestStopIteration
|
| 265 |
+
|
| 266 |
+
Algorithm: expr.base is in the extension Q_m = Q(sqrt(r_1),..,sqrt(r_k));
|
| 267 |
+
split expr.base = a + b*sqrt(r_k), where `a` and `b` are on
|
| 268 |
+
Q_(m-1) = Q(sqrt(r_1),..,sqrt(r_(k-1))); then a**2 - b**2*r_k is
|
| 269 |
+
on Q_(m-1); denest sqrt(a**2 - b**2*r_k) and so on.
|
| 270 |
+
See [1], section 6.
|
| 271 |
+
|
| 272 |
+
Examples
|
| 273 |
+
========
|
| 274 |
+
|
| 275 |
+
>>> from sympy import sqrt
|
| 276 |
+
>>> from sympy.simplify.sqrtdenest import _sqrtdenest_rec
|
| 277 |
+
>>> _sqrtdenest_rec(sqrt(-72*sqrt(2) + 158*sqrt(5) + 498))
|
| 278 |
+
-sqrt(10) + sqrt(2) + 9 + 9*sqrt(5)
|
| 279 |
+
>>> w=-6*sqrt(55)-6*sqrt(35)-2*sqrt(22)-2*sqrt(14)+2*sqrt(77)+6*sqrt(10)+65
|
| 280 |
+
>>> _sqrtdenest_rec(sqrt(w))
|
| 281 |
+
-sqrt(11) - sqrt(7) + sqrt(2) + 3*sqrt(5)
|
| 282 |
+
"""
|
| 283 |
+
from sympy.simplify.radsimp import radsimp, rad_rationalize, split_surds
|
| 284 |
+
if not expr.is_Pow:
|
| 285 |
+
return sqrtdenest(expr)
|
| 286 |
+
if expr.base < 0:
|
| 287 |
+
return sqrt(-1)*_sqrtdenest_rec(sqrt(-expr.base))
|
| 288 |
+
g, a, b = split_surds(expr.base)
|
| 289 |
+
a = a*sqrt(g)
|
| 290 |
+
if a < b:
|
| 291 |
+
a, b = b, a
|
| 292 |
+
c2 = _mexpand(a**2 - b**2)
|
| 293 |
+
if len(c2.args) > 2:
|
| 294 |
+
g, a1, b1 = split_surds(c2)
|
| 295 |
+
a1 = a1*sqrt(g)
|
| 296 |
+
if a1 < b1:
|
| 297 |
+
a1, b1 = b1, a1
|
| 298 |
+
c2_1 = _mexpand(a1**2 - b1**2)
|
| 299 |
+
c_1 = _sqrtdenest_rec(sqrt(c2_1))
|
| 300 |
+
d_1 = _sqrtdenest_rec(sqrt(a1 + c_1))
|
| 301 |
+
num, den = rad_rationalize(b1, d_1)
|
| 302 |
+
c = _mexpand(d_1/sqrt(2) + num/(den*sqrt(2)))
|
| 303 |
+
else:
|
| 304 |
+
c = _sqrtdenest1(sqrt(c2))
|
| 305 |
+
|
| 306 |
+
if sqrt_depth(c) > 1:
|
| 307 |
+
raise SqrtdenestStopIteration
|
| 308 |
+
ac = a + c
|
| 309 |
+
if len(ac.args) >= len(expr.args):
|
| 310 |
+
if count_ops(ac) >= count_ops(expr.base):
|
| 311 |
+
raise SqrtdenestStopIteration
|
| 312 |
+
d = sqrtdenest(sqrt(ac))
|
| 313 |
+
if sqrt_depth(d) > 1:
|
| 314 |
+
raise SqrtdenestStopIteration
|
| 315 |
+
num, den = rad_rationalize(b, d)
|
| 316 |
+
r = d/sqrt(2) + num/(den*sqrt(2))
|
| 317 |
+
r = radsimp(r)
|
| 318 |
+
return _mexpand(r)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
def _sqrtdenest1(expr, denester=True):
|
| 322 |
+
"""Return denested expr after denesting with simpler methods or, that
|
| 323 |
+
failing, using the denester."""
|
| 324 |
+
|
| 325 |
+
from sympy.simplify.simplify import radsimp
|
| 326 |
+
|
| 327 |
+
if not is_sqrt(expr):
|
| 328 |
+
return expr
|
| 329 |
+
|
| 330 |
+
a = expr.base
|
| 331 |
+
if a.is_Atom:
|
| 332 |
+
return expr
|
| 333 |
+
val = _sqrt_match(a)
|
| 334 |
+
if not val:
|
| 335 |
+
return expr
|
| 336 |
+
|
| 337 |
+
a, b, r = val
|
| 338 |
+
# try a quick numeric denesting
|
| 339 |
+
d2 = _mexpand(a**2 - b**2*r)
|
| 340 |
+
if d2.is_Rational:
|
| 341 |
+
if d2.is_positive:
|
| 342 |
+
z = _sqrt_numeric_denest(a, b, r, d2)
|
| 343 |
+
if z is not None:
|
| 344 |
+
return z
|
| 345 |
+
else:
|
| 346 |
+
# fourth root case
|
| 347 |
+
# sqrtdenest(sqrt(3 + 2*sqrt(3))) =
|
| 348 |
+
# sqrt(2)*3**(1/4)/2 + sqrt(2)*3**(3/4)/2
|
| 349 |
+
dr2 = _mexpand(-d2*r)
|
| 350 |
+
dr = sqrt(dr2)
|
| 351 |
+
if dr.is_Rational:
|
| 352 |
+
z = _sqrt_numeric_denest(_mexpand(b*r), a, r, dr2)
|
| 353 |
+
if z is not None:
|
| 354 |
+
return z/root(r, 4)
|
| 355 |
+
|
| 356 |
+
else:
|
| 357 |
+
z = _sqrt_symbolic_denest(a, b, r)
|
| 358 |
+
if z is not None:
|
| 359 |
+
return z
|
| 360 |
+
|
| 361 |
+
if not denester or not is_algebraic(expr):
|
| 362 |
+
return expr
|
| 363 |
+
|
| 364 |
+
res = sqrt_biquadratic_denest(expr, a, b, r, d2)
|
| 365 |
+
if res:
|
| 366 |
+
return res
|
| 367 |
+
|
| 368 |
+
# now call to the denester
|
| 369 |
+
av0 = [a, b, r, d2]
|
| 370 |
+
z = _denester([radsimp(expr**2)], av0, 0, sqrt_depth(expr))[0]
|
| 371 |
+
if av0[1] is None:
|
| 372 |
+
return expr
|
| 373 |
+
if z is not None:
|
| 374 |
+
if sqrt_depth(z) == sqrt_depth(expr) and count_ops(z) > count_ops(expr):
|
| 375 |
+
return expr
|
| 376 |
+
return z
|
| 377 |
+
return expr
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def _sqrt_symbolic_denest(a, b, r):
|
| 381 |
+
"""Given an expression, sqrt(a + b*sqrt(b)), return the denested
|
| 382 |
+
expression or None.
|
| 383 |
+
|
| 384 |
+
Explanation
|
| 385 |
+
===========
|
| 386 |
+
|
| 387 |
+
If r = ra + rb*sqrt(rr), try replacing sqrt(rr) in ``a`` with
|
| 388 |
+
(y**2 - ra)/rb, and if the result is a quadratic, ca*y**2 + cb*y + cc, and
|
| 389 |
+
(cb + b)**2 - 4*ca*cc is 0, then sqrt(a + b*sqrt(r)) can be rewritten as
|
| 390 |
+
sqrt(ca*(sqrt(r) + (cb + b)/(2*ca))**2).
|
| 391 |
+
|
| 392 |
+
Examples
|
| 393 |
+
========
|
| 394 |
+
|
| 395 |
+
>>> from sympy.simplify.sqrtdenest import _sqrt_symbolic_denest, sqrtdenest
|
| 396 |
+
>>> from sympy import sqrt, Symbol
|
| 397 |
+
>>> from sympy.abc import x
|
| 398 |
+
|
| 399 |
+
>>> a, b, r = 16 - 2*sqrt(29), 2, -10*sqrt(29) + 55
|
| 400 |
+
>>> _sqrt_symbolic_denest(a, b, r)
|
| 401 |
+
sqrt(11 - 2*sqrt(29)) + sqrt(5)
|
| 402 |
+
|
| 403 |
+
If the expression is numeric, it will be simplified:
|
| 404 |
+
|
| 405 |
+
>>> w = sqrt(sqrt(sqrt(3) + 1) + 1) + 1 + sqrt(2)
|
| 406 |
+
>>> sqrtdenest(sqrt((w**2).expand()))
|
| 407 |
+
1 + sqrt(2) + sqrt(1 + sqrt(1 + sqrt(3)))
|
| 408 |
+
|
| 409 |
+
Otherwise, it will only be simplified if assumptions allow:
|
| 410 |
+
|
| 411 |
+
>>> w = w.subs(sqrt(3), sqrt(x + 3))
|
| 412 |
+
>>> sqrtdenest(sqrt((w**2).expand()))
|
| 413 |
+
sqrt((sqrt(sqrt(sqrt(x + 3) + 1) + 1) + 1 + sqrt(2))**2)
|
| 414 |
+
|
| 415 |
+
Notice that the argument of the sqrt is a square. If x is made positive
|
| 416 |
+
then the sqrt of the square is resolved:
|
| 417 |
+
|
| 418 |
+
>>> _.subs(x, Symbol('x', positive=True))
|
| 419 |
+
sqrt(sqrt(sqrt(x + 3) + 1) + 1) + 1 + sqrt(2)
|
| 420 |
+
"""
|
| 421 |
+
|
| 422 |
+
a, b, r = map(sympify, (a, b, r))
|
| 423 |
+
rval = _sqrt_match(r)
|
| 424 |
+
if not rval:
|
| 425 |
+
return None
|
| 426 |
+
ra, rb, rr = rval
|
| 427 |
+
if rb:
|
| 428 |
+
y = Dummy('y', positive=True)
|
| 429 |
+
try:
|
| 430 |
+
newa = Poly(a.subs(sqrt(rr), (y**2 - ra)/rb), y)
|
| 431 |
+
except PolynomialError:
|
| 432 |
+
return None
|
| 433 |
+
if newa.degree() == 2:
|
| 434 |
+
ca, cb, cc = newa.all_coeffs()
|
| 435 |
+
cb += b
|
| 436 |
+
if _mexpand(cb**2 - 4*ca*cc).equals(0):
|
| 437 |
+
z = sqrt(ca*(sqrt(r) + cb/(2*ca))**2)
|
| 438 |
+
if z.is_number:
|
| 439 |
+
z = _mexpand(Mul._from_args(z.as_content_primitive()))
|
| 440 |
+
return z
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
def _sqrt_numeric_denest(a, b, r, d2):
|
| 444 |
+
r"""Helper that denest
|
| 445 |
+
$\sqrt{a + b \sqrt{r}}, d^2 = a^2 - b^2 r > 0$
|
| 446 |
+
|
| 447 |
+
If it cannot be denested, it returns ``None``.
|
| 448 |
+
"""
|
| 449 |
+
d = sqrt(d2)
|
| 450 |
+
s = a + d
|
| 451 |
+
# sqrt_depth(res) <= sqrt_depth(s) + 1
|
| 452 |
+
# sqrt_depth(expr) = sqrt_depth(r) + 2
|
| 453 |
+
# there is denesting if sqrt_depth(s) + 1 < sqrt_depth(r) + 2
|
| 454 |
+
# if s**2 is Number there is a fourth root
|
| 455 |
+
if sqrt_depth(s) < sqrt_depth(r) + 1 or (s**2).is_Rational:
|
| 456 |
+
s1, s2 = sign(s), sign(b)
|
| 457 |
+
if s1 == s2 == -1:
|
| 458 |
+
s1 = s2 = 1
|
| 459 |
+
res = (s1 * sqrt(a + d) + s2 * sqrt(a - d)) * sqrt(2) / 2
|
| 460 |
+
return res.expand()
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
def sqrt_biquadratic_denest(expr, a, b, r, d2):
|
| 464 |
+
"""denest expr = sqrt(a + b*sqrt(r))
|
| 465 |
+
where a, b, r are linear combinations of square roots of
|
| 466 |
+
positive rationals on the rationals (SQRR) and r > 0, b != 0,
|
| 467 |
+
d2 = a**2 - b**2*r > 0
|
| 468 |
+
|
| 469 |
+
If it cannot denest it returns None.
|
| 470 |
+
|
| 471 |
+
Explanation
|
| 472 |
+
===========
|
| 473 |
+
|
| 474 |
+
Search for a solution A of type SQRR of the biquadratic equation
|
| 475 |
+
4*A**4 - 4*a*A**2 + b**2*r = 0 (1)
|
| 476 |
+
sqd = sqrt(a**2 - b**2*r)
|
| 477 |
+
Choosing the sqrt to be positive, the possible solutions are
|
| 478 |
+
A = sqrt(a/2 +/- sqd/2)
|
| 479 |
+
Since a, b, r are SQRR, then a**2 - b**2*r is a SQRR,
|
| 480 |
+
so if sqd can be denested, it is done by
|
| 481 |
+
_sqrtdenest_rec, and the result is a SQRR.
|
| 482 |
+
Similarly for A.
|
| 483 |
+
Examples of solutions (in both cases a and sqd are positive):
|
| 484 |
+
|
| 485 |
+
Example of expr with solution sqrt(a/2 + sqd/2) but not
|
| 486 |
+
solution sqrt(a/2 - sqd/2):
|
| 487 |
+
expr = sqrt(-sqrt(15) - sqrt(2)*sqrt(-sqrt(5) + 5) - sqrt(3) + 8)
|
| 488 |
+
a = -sqrt(15) - sqrt(3) + 8; sqd = -2*sqrt(5) - 2 + 4*sqrt(3)
|
| 489 |
+
|
| 490 |
+
Example of expr with solution sqrt(a/2 - sqd/2) but not
|
| 491 |
+
solution sqrt(a/2 + sqd/2):
|
| 492 |
+
w = 2 + r2 + r3 + (1 + r3)*sqrt(2 + r2 + 5*r3)
|
| 493 |
+
expr = sqrt((w**2).expand())
|
| 494 |
+
a = 4*sqrt(6) + 8*sqrt(2) + 47 + 28*sqrt(3)
|
| 495 |
+
sqd = 29 + 20*sqrt(3)
|
| 496 |
+
|
| 497 |
+
Define B = b/2*A; eq.(1) implies a = A**2 + B**2*r; then
|
| 498 |
+
expr**2 = a + b*sqrt(r) = (A + B*sqrt(r))**2
|
| 499 |
+
|
| 500 |
+
Examples
|
| 501 |
+
========
|
| 502 |
+
|
| 503 |
+
>>> from sympy import sqrt
|
| 504 |
+
>>> from sympy.simplify.sqrtdenest import _sqrt_match, sqrt_biquadratic_denest
|
| 505 |
+
>>> z = sqrt((2*sqrt(2) + 4)*sqrt(2 + sqrt(2)) + 5*sqrt(2) + 8)
|
| 506 |
+
>>> a, b, r = _sqrt_match(z**2)
|
| 507 |
+
>>> d2 = a**2 - b**2*r
|
| 508 |
+
>>> sqrt_biquadratic_denest(z, a, b, r, d2)
|
| 509 |
+
sqrt(2) + sqrt(sqrt(2) + 2) + 2
|
| 510 |
+
"""
|
| 511 |
+
from sympy.simplify.radsimp import radsimp, rad_rationalize
|
| 512 |
+
if r <= 0 or d2 < 0 or not b or sqrt_depth(expr.base) < 2:
|
| 513 |
+
return None
|
| 514 |
+
for x in (a, b, r):
|
| 515 |
+
for y in x.args:
|
| 516 |
+
y2 = y**2
|
| 517 |
+
if not y2.is_Integer or not y2.is_positive:
|
| 518 |
+
return None
|
| 519 |
+
sqd = _mexpand(sqrtdenest(sqrt(radsimp(d2))))
|
| 520 |
+
if sqrt_depth(sqd) > 1:
|
| 521 |
+
return None
|
| 522 |
+
x1, x2 = [a/2 + sqd/2, a/2 - sqd/2]
|
| 523 |
+
# look for a solution A with depth 1
|
| 524 |
+
for x in (x1, x2):
|
| 525 |
+
A = sqrtdenest(sqrt(x))
|
| 526 |
+
if sqrt_depth(A) > 1:
|
| 527 |
+
continue
|
| 528 |
+
Bn, Bd = rad_rationalize(b, _mexpand(2*A))
|
| 529 |
+
B = Bn/Bd
|
| 530 |
+
z = A + B*sqrt(r)
|
| 531 |
+
if z < 0:
|
| 532 |
+
z = -z
|
| 533 |
+
return _mexpand(z)
|
| 534 |
+
return None
|
| 535 |
+
|
| 536 |
+
|
| 537 |
+
def _denester(nested, av0, h, max_depth_level):
|
| 538 |
+
"""Denests a list of expressions that contain nested square roots.
|
| 539 |
+
|
| 540 |
+
Explanation
|
| 541 |
+
===========
|
| 542 |
+
|
| 543 |
+
Algorithm based on <http://www.almaden.ibm.com/cs/people/fagin/symb85.pdf>.
|
| 544 |
+
|
| 545 |
+
It is assumed that all of the elements of 'nested' share the same
|
| 546 |
+
bottom-level radicand. (This is stated in the paper, on page 177, in
|
| 547 |
+
the paragraph immediately preceding the algorithm.)
|
| 548 |
+
|
| 549 |
+
When evaluating all of the arguments in parallel, the bottom-level
|
| 550 |
+
radicand only needs to be denested once. This means that calling
|
| 551 |
+
_denester with x arguments results in a recursive invocation with x+1
|
| 552 |
+
arguments; hence _denester has polynomial complexity.
|
| 553 |
+
|
| 554 |
+
However, if the arguments were evaluated separately, each call would
|
| 555 |
+
result in two recursive invocations, and the algorithm would have
|
| 556 |
+
exponential complexity.
|
| 557 |
+
|
| 558 |
+
This is discussed in the paper in the middle paragraph of page 179.
|
| 559 |
+
"""
|
| 560 |
+
from sympy.simplify.simplify import radsimp
|
| 561 |
+
if h > max_depth_level:
|
| 562 |
+
return None, None
|
| 563 |
+
if av0[1] is None:
|
| 564 |
+
return None, None
|
| 565 |
+
if (av0[0] is None and
|
| 566 |
+
all(n.is_Number for n in nested)): # no arguments are nested
|
| 567 |
+
for f in _subsets(len(nested)): # test subset 'f' of nested
|
| 568 |
+
p = _mexpand(Mul(*[nested[i] for i in range(len(f)) if f[i]]))
|
| 569 |
+
if f.count(1) > 1 and f[-1]:
|
| 570 |
+
p = -p
|
| 571 |
+
sqp = sqrt(p)
|
| 572 |
+
if sqp.is_Rational:
|
| 573 |
+
return sqp, f # got a perfect square so return its square root.
|
| 574 |
+
# Otherwise, return the radicand from the previous invocation.
|
| 575 |
+
return sqrt(nested[-1]), [0]*len(nested)
|
| 576 |
+
else:
|
| 577 |
+
R = None
|
| 578 |
+
if av0[0] is not None:
|
| 579 |
+
values = [av0[:2]]
|
| 580 |
+
R = av0[2]
|
| 581 |
+
nested2 = [av0[3], R]
|
| 582 |
+
av0[0] = None
|
| 583 |
+
else:
|
| 584 |
+
values = list(filter(None, [_sqrt_match(expr) for expr in nested]))
|
| 585 |
+
for v in values:
|
| 586 |
+
if v[2]: # Since if b=0, r is not defined
|
| 587 |
+
if R is not None:
|
| 588 |
+
if R != v[2]:
|
| 589 |
+
av0[1] = None
|
| 590 |
+
return None, None
|
| 591 |
+
else:
|
| 592 |
+
R = v[2]
|
| 593 |
+
if R is None:
|
| 594 |
+
# return the radicand from the previous invocation
|
| 595 |
+
return sqrt(nested[-1]), [0]*len(nested)
|
| 596 |
+
nested2 = [_mexpand(v[0]**2) -
|
| 597 |
+
_mexpand(R*v[1]**2) for v in values] + [R]
|
| 598 |
+
d, f = _denester(nested2, av0, h + 1, max_depth_level)
|
| 599 |
+
if not f:
|
| 600 |
+
return None, None
|
| 601 |
+
if not any(f[i] for i in range(len(nested))):
|
| 602 |
+
v = values[-1]
|
| 603 |
+
return sqrt(v[0] + _mexpand(v[1]*d)), f
|
| 604 |
+
else:
|
| 605 |
+
p = Mul(*[nested[i] for i in range(len(nested)) if f[i]])
|
| 606 |
+
v = _sqrt_match(p)
|
| 607 |
+
if 1 in f and f.index(1) < len(nested) - 1 and f[len(nested) - 1]:
|
| 608 |
+
v[0] = -v[0]
|
| 609 |
+
v[1] = -v[1]
|
| 610 |
+
if not f[len(nested)]: # Solution denests with square roots
|
| 611 |
+
vad = _mexpand(v[0] + d)
|
| 612 |
+
if vad <= 0:
|
| 613 |
+
# return the radicand from the previous invocation.
|
| 614 |
+
return sqrt(nested[-1]), [0]*len(nested)
|
| 615 |
+
if not(sqrt_depth(vad) <= sqrt_depth(R) + 1 or
|
| 616 |
+
(vad**2).is_Number):
|
| 617 |
+
av0[1] = None
|
| 618 |
+
return None, None
|
| 619 |
+
|
| 620 |
+
sqvad = _sqrtdenest1(sqrt(vad), denester=False)
|
| 621 |
+
if not (sqrt_depth(sqvad) <= sqrt_depth(R) + 1):
|
| 622 |
+
av0[1] = None
|
| 623 |
+
return None, None
|
| 624 |
+
sqvad1 = radsimp(1/sqvad)
|
| 625 |
+
res = _mexpand(sqvad/sqrt(2) + (v[1]*sqrt(R)*sqvad1/sqrt(2)))
|
| 626 |
+
return res, f
|
| 627 |
+
|
| 628 |
+
# sign(v[1])*sqrt(_mexpand(v[1]**2*R*vad1/2))), f
|
| 629 |
+
else: # Solution requires a fourth root
|
| 630 |
+
s2 = _mexpand(v[1]*R) + d
|
| 631 |
+
if s2 <= 0:
|
| 632 |
+
return sqrt(nested[-1]), [0]*len(nested)
|
| 633 |
+
FR, s = root(_mexpand(R), 4), sqrt(s2)
|
| 634 |
+
return _mexpand(s/(sqrt(2)*FR) + v[0]*FR/(sqrt(2)*s)), f
|
| 635 |
+
|
| 636 |
+
|
| 637 |
+
def _sqrt_ratcomb(cs, args):
|
| 638 |
+
"""Denest rational combinations of radicals.
|
| 639 |
+
|
| 640 |
+
Based on section 5 of [1].
|
| 641 |
+
|
| 642 |
+
Examples
|
| 643 |
+
========
|
| 644 |
+
|
| 645 |
+
>>> from sympy import sqrt
|
| 646 |
+
>>> from sympy.simplify.sqrtdenest import sqrtdenest
|
| 647 |
+
>>> z = sqrt(1+sqrt(3)) + sqrt(3+3*sqrt(3)) - sqrt(10+6*sqrt(3))
|
| 648 |
+
>>> sqrtdenest(z)
|
| 649 |
+
0
|
| 650 |
+
"""
|
| 651 |
+
from sympy.simplify.radsimp import radsimp
|
| 652 |
+
|
| 653 |
+
# check if there exists a pair of sqrt that can be denested
|
| 654 |
+
def find(a):
|
| 655 |
+
n = len(a)
|
| 656 |
+
for i in range(n - 1):
|
| 657 |
+
for j in range(i + 1, n):
|
| 658 |
+
s1 = a[i].base
|
| 659 |
+
s2 = a[j].base
|
| 660 |
+
p = _mexpand(s1 * s2)
|
| 661 |
+
s = sqrtdenest(sqrt(p))
|
| 662 |
+
if s != sqrt(p):
|
| 663 |
+
return s, i, j
|
| 664 |
+
|
| 665 |
+
indices = find(args)
|
| 666 |
+
if indices is None:
|
| 667 |
+
return Add(*[c * arg for c, arg in zip(cs, args)])
|
| 668 |
+
|
| 669 |
+
s, i1, i2 = indices
|
| 670 |
+
|
| 671 |
+
c2 = cs.pop(i2)
|
| 672 |
+
args.pop(i2)
|
| 673 |
+
a1 = args[i1]
|
| 674 |
+
|
| 675 |
+
# replace a2 by s/a1
|
| 676 |
+
cs[i1] += radsimp(c2 * s / a1.base)
|
| 677 |
+
|
| 678 |
+
return _sqrt_ratcomb(cs, args)
|
lib/python3.12/site-packages/sympy/simplify/tests/__init__.py
ADDED
|
File without changes
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (199 Bytes). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_combsimp.cpython-312.pyc
ADDED
|
Binary file (6.19 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_cse.cpython-312.pyc
ADDED
|
Binary file (55.9 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_epathtools.cpython-312.pyc
ADDED
|
Binary file (8.05 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_fu.cpython-312.pyc
ADDED
|
Binary file (58.5 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_function.cpython-312.pyc
ADDED
|
Binary file (4.73 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_gammasimp.cpython-312.pyc
ADDED
|
Binary file (13.9 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_hyperexpand.cpython-312.pyc
ADDED
|
Binary file (84.5 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_powsimp.cpython-312.pyc
ADDED
|
Binary file (33.2 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_radsimp.cpython-312.pyc
ADDED
|
Binary file (47.6 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_ratsimp.cpython-312.pyc
ADDED
|
Binary file (6.2 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_rewrite.cpython-312.pyc
ADDED
|
Binary file (3.29 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_simplify.cpython-312.pyc
ADDED
|
Binary file (98.2 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_sqrtdenest.cpython-312.pyc
ADDED
|
Binary file (19.6 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/__pycache__/test_trigsimp.cpython-312.pyc
ADDED
|
Binary file (51.4 kB). View file
|
|
|
lib/python3.12/site-packages/sympy/simplify/tests/test_combsimp.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.numbers import Rational
|
| 2 |
+
from sympy.core.symbol import symbols
|
| 3 |
+
from sympy.functions.combinatorial.factorials import (FallingFactorial, RisingFactorial, binomial, factorial)
|
| 4 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 5 |
+
from sympy.simplify.combsimp import combsimp
|
| 6 |
+
from sympy.abc import x
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_combsimp():
|
| 10 |
+
k, m, n = symbols('k m n', integer = True)
|
| 11 |
+
|
| 12 |
+
assert combsimp(factorial(n)) == factorial(n)
|
| 13 |
+
assert combsimp(binomial(n, k)) == binomial(n, k)
|
| 14 |
+
|
| 15 |
+
assert combsimp(factorial(n)/factorial(n - 3)) == n*(-1 + n)*(-2 + n)
|
| 16 |
+
assert combsimp(binomial(n + 1, k + 1)/binomial(n, k)) == (1 + n)/(1 + k)
|
| 17 |
+
|
| 18 |
+
assert combsimp(binomial(3*n + 4, n + 1)/binomial(3*n + 1, n)) == \
|
| 19 |
+
Rational(3, 2)*((3*n + 2)*(3*n + 4)/((n + 1)*(2*n + 3)))
|
| 20 |
+
|
| 21 |
+
assert combsimp(factorial(n)**2/factorial(n - 3)) == \
|
| 22 |
+
factorial(n)*n*(-1 + n)*(-2 + n)
|
| 23 |
+
assert combsimp(factorial(n)*binomial(n + 1, k + 1)/binomial(n, k)) == \
|
| 24 |
+
factorial(n + 1)/(1 + k)
|
| 25 |
+
|
| 26 |
+
assert combsimp(gamma(n + 3)) == factorial(n + 2)
|
| 27 |
+
|
| 28 |
+
assert combsimp(factorial(x)) == gamma(x + 1)
|
| 29 |
+
|
| 30 |
+
# issue 9699
|
| 31 |
+
assert combsimp((n + 1)*factorial(n)) == factorial(n + 1)
|
| 32 |
+
assert combsimp(factorial(n)/n) == factorial(n-1)
|
| 33 |
+
|
| 34 |
+
# issue 6658
|
| 35 |
+
assert combsimp(binomial(n, n - k)) == binomial(n, k)
|
| 36 |
+
|
| 37 |
+
# issue 6341, 7135
|
| 38 |
+
assert combsimp(factorial(n)/(factorial(k)*factorial(n - k))) == \
|
| 39 |
+
binomial(n, k)
|
| 40 |
+
assert combsimp(factorial(k)*factorial(n - k)/factorial(n)) == \
|
| 41 |
+
1/binomial(n, k)
|
| 42 |
+
assert combsimp(factorial(2*n)/factorial(n)**2) == binomial(2*n, n)
|
| 43 |
+
assert combsimp(factorial(2*n)*factorial(k)*factorial(n - k)/
|
| 44 |
+
factorial(n)**3) == binomial(2*n, n)/binomial(n, k)
|
| 45 |
+
|
| 46 |
+
assert combsimp(factorial(n*(1 + n) - n**2 - n)) == 1
|
| 47 |
+
|
| 48 |
+
assert combsimp(6*FallingFactorial(-4, n)/factorial(n)) == \
|
| 49 |
+
(-1)**n*(n + 1)*(n + 2)*(n + 3)
|
| 50 |
+
assert combsimp(6*FallingFactorial(-4, n - 1)/factorial(n - 1)) == \
|
| 51 |
+
(-1)**(n - 1)*n*(n + 1)*(n + 2)
|
| 52 |
+
assert combsimp(6*FallingFactorial(-4, n - 3)/factorial(n - 3)) == \
|
| 53 |
+
(-1)**(n - 3)*n*(n - 1)*(n - 2)
|
| 54 |
+
assert combsimp(6*FallingFactorial(-4, -n - 1)/factorial(-n - 1)) == \
|
| 55 |
+
-(-1)**(-n - 1)*n*(n - 1)*(n - 2)
|
| 56 |
+
|
| 57 |
+
assert combsimp(6*RisingFactorial(4, n)/factorial(n)) == \
|
| 58 |
+
(n + 1)*(n + 2)*(n + 3)
|
| 59 |
+
assert combsimp(6*RisingFactorial(4, n - 1)/factorial(n - 1)) == \
|
| 60 |
+
n*(n + 1)*(n + 2)
|
| 61 |
+
assert combsimp(6*RisingFactorial(4, n - 3)/factorial(n - 3)) == \
|
| 62 |
+
n*(n - 1)*(n - 2)
|
| 63 |
+
assert combsimp(6*RisingFactorial(4, -n - 1)/factorial(-n - 1)) == \
|
| 64 |
+
-n*(n - 1)*(n - 2)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def test_issue_6878():
|
| 68 |
+
n = symbols('n', integer=True)
|
| 69 |
+
assert combsimp(RisingFactorial(-10, n)) == 3628800*(-1)**n/factorial(10 - n)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def test_issue_14528():
|
| 73 |
+
p = symbols("p", integer=True, positive=True)
|
| 74 |
+
assert combsimp(binomial(1,p)) == 1/(factorial(p)*factorial(1-p))
|
| 75 |
+
assert combsimp(factorial(2-p)) == factorial(2-p)
|
lib/python3.12/site-packages/sympy/simplify/tests/test_cse.py
ADDED
|
@@ -0,0 +1,758 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from functools import reduce
|
| 2 |
+
import itertools
|
| 3 |
+
from operator import add
|
| 4 |
+
|
| 5 |
+
from sympy.codegen.matrix_nodes import MatrixSolve
|
| 6 |
+
from sympy.core.add import Add
|
| 7 |
+
from sympy.core.containers import Tuple
|
| 8 |
+
from sympy.core.expr import UnevaluatedExpr
|
| 9 |
+
from sympy.core.function import Function
|
| 10 |
+
from sympy.core.mul import Mul
|
| 11 |
+
from sympy.core.power import Pow
|
| 12 |
+
from sympy.core.relational import Eq
|
| 13 |
+
from sympy.core.singleton import S
|
| 14 |
+
from sympy.core.symbol import (Symbol, symbols)
|
| 15 |
+
from sympy.core.sympify import sympify
|
| 16 |
+
from sympy.functions.elementary.exponential import exp
|
| 17 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 18 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 19 |
+
from sympy.functions.elementary.trigonometric import (cos, sin)
|
| 20 |
+
from sympy.matrices.dense import Matrix
|
| 21 |
+
from sympy.matrices.expressions import Inverse, MatAdd, MatMul, Transpose
|
| 22 |
+
from sympy.polys.rootoftools import CRootOf
|
| 23 |
+
from sympy.series.order import O
|
| 24 |
+
from sympy.simplify.cse_main import cse
|
| 25 |
+
from sympy.simplify.simplify import signsimp
|
| 26 |
+
from sympy.tensor.indexed import (Idx, IndexedBase)
|
| 27 |
+
|
| 28 |
+
from sympy.core.function import count_ops
|
| 29 |
+
from sympy.simplify.cse_opts import sub_pre, sub_post
|
| 30 |
+
from sympy.functions.special.hyper import meijerg
|
| 31 |
+
from sympy.simplify import cse_main, cse_opts
|
| 32 |
+
from sympy.utilities.iterables import subsets
|
| 33 |
+
from sympy.testing.pytest import XFAIL, raises
|
| 34 |
+
from sympy.matrices import (MutableDenseMatrix, MutableSparseMatrix,
|
| 35 |
+
ImmutableDenseMatrix, ImmutableSparseMatrix)
|
| 36 |
+
from sympy.matrices.expressions import MatrixSymbol
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
w, x, y, z = symbols('w,x,y,z')
|
| 40 |
+
x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12 = symbols('x:13')
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def test_numbered_symbols():
|
| 44 |
+
ns = cse_main.numbered_symbols(prefix='y')
|
| 45 |
+
assert list(itertools.islice(
|
| 46 |
+
ns, 0, 10)) == [Symbol('y%s' % i) for i in range(0, 10)]
|
| 47 |
+
ns = cse_main.numbered_symbols(prefix='y')
|
| 48 |
+
assert list(itertools.islice(
|
| 49 |
+
ns, 10, 20)) == [Symbol('y%s' % i) for i in range(10, 20)]
|
| 50 |
+
ns = cse_main.numbered_symbols()
|
| 51 |
+
assert list(itertools.islice(
|
| 52 |
+
ns, 0, 10)) == [Symbol('x%s' % i) for i in range(0, 10)]
|
| 53 |
+
|
| 54 |
+
# Dummy "optimization" functions for testing.
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def opt1(expr):
|
| 58 |
+
return expr + y
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def opt2(expr):
|
| 62 |
+
return expr*z
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def test_preprocess_for_cse():
|
| 66 |
+
assert cse_main.preprocess_for_cse(x, [(opt1, None)]) == x + y
|
| 67 |
+
assert cse_main.preprocess_for_cse(x, [(None, opt1)]) == x
|
| 68 |
+
assert cse_main.preprocess_for_cse(x, [(None, None)]) == x
|
| 69 |
+
assert cse_main.preprocess_for_cse(x, [(opt1, opt2)]) == x + y
|
| 70 |
+
assert cse_main.preprocess_for_cse(
|
| 71 |
+
x, [(opt1, None), (opt2, None)]) == (x + y)*z
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def test_postprocess_for_cse():
|
| 75 |
+
assert cse_main.postprocess_for_cse(x, [(opt1, None)]) == x
|
| 76 |
+
assert cse_main.postprocess_for_cse(x, [(None, opt1)]) == x + y
|
| 77 |
+
assert cse_main.postprocess_for_cse(x, [(None, None)]) == x
|
| 78 |
+
assert cse_main.postprocess_for_cse(x, [(opt1, opt2)]) == x*z
|
| 79 |
+
# Note the reverse order of application.
|
| 80 |
+
assert cse_main.postprocess_for_cse(
|
| 81 |
+
x, [(None, opt1), (None, opt2)]) == x*z + y
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def test_cse_single():
|
| 85 |
+
# Simple substitution.
|
| 86 |
+
e = Add(Pow(x + y, 2), sqrt(x + y))
|
| 87 |
+
substs, reduced = cse([e])
|
| 88 |
+
assert substs == [(x0, x + y)]
|
| 89 |
+
assert reduced == [sqrt(x0) + x0**2]
|
| 90 |
+
|
| 91 |
+
subst42, (red42,) = cse([42]) # issue_15082
|
| 92 |
+
assert len(subst42) == 0 and red42 == 42
|
| 93 |
+
subst_half, (red_half,) = cse([0.5])
|
| 94 |
+
assert len(subst_half) == 0 and red_half == 0.5
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def test_cse_single2():
|
| 98 |
+
# Simple substitution, test for being able to pass the expression directly
|
| 99 |
+
e = Add(Pow(x + y, 2), sqrt(x + y))
|
| 100 |
+
substs, reduced = cse(e)
|
| 101 |
+
assert substs == [(x0, x + y)]
|
| 102 |
+
assert reduced == [sqrt(x0) + x0**2]
|
| 103 |
+
substs, reduced = cse(Matrix([[1]]))
|
| 104 |
+
assert isinstance(reduced[0], Matrix)
|
| 105 |
+
|
| 106 |
+
subst42, (red42,) = cse(42) # issue 15082
|
| 107 |
+
assert len(subst42) == 0 and red42 == 42
|
| 108 |
+
subst_half, (red_half,) = cse(0.5) # issue 15082
|
| 109 |
+
assert len(subst_half) == 0 and red_half == 0.5
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def test_cse_not_possible():
|
| 113 |
+
# No substitution possible.
|
| 114 |
+
e = Add(x, y)
|
| 115 |
+
substs, reduced = cse([e])
|
| 116 |
+
assert substs == []
|
| 117 |
+
assert reduced == [x + y]
|
| 118 |
+
# issue 6329
|
| 119 |
+
eq = (meijerg((1, 2), (y, 4), (5,), [], x) +
|
| 120 |
+
meijerg((1, 3), (y, 4), (5,), [], x))
|
| 121 |
+
assert cse(eq) == ([], [eq])
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def test_nested_substitution():
|
| 125 |
+
# Substitution within a substitution.
|
| 126 |
+
e = Add(Pow(w*x + y, 2), sqrt(w*x + y))
|
| 127 |
+
substs, reduced = cse([e])
|
| 128 |
+
assert substs == [(x0, w*x + y)]
|
| 129 |
+
assert reduced == [sqrt(x0) + x0**2]
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def test_subtraction_opt():
|
| 133 |
+
# Make sure subtraction is optimized.
|
| 134 |
+
e = (x - y)*(z - y) + exp((x - y)*(z - y))
|
| 135 |
+
substs, reduced = cse(
|
| 136 |
+
[e], optimizations=[(cse_opts.sub_pre, cse_opts.sub_post)])
|
| 137 |
+
assert substs == [(x0, (x - y)*(y - z))]
|
| 138 |
+
assert reduced == [-x0 + exp(-x0)]
|
| 139 |
+
e = -(x - y)*(z - y) + exp(-(x - y)*(z - y))
|
| 140 |
+
substs, reduced = cse(
|
| 141 |
+
[e], optimizations=[(cse_opts.sub_pre, cse_opts.sub_post)])
|
| 142 |
+
assert substs == [(x0, (x - y)*(y - z))]
|
| 143 |
+
assert reduced == [x0 + exp(x0)]
|
| 144 |
+
# issue 4077
|
| 145 |
+
n = -1 + 1/x
|
| 146 |
+
e = n/x/(-n)**2 - 1/n/x
|
| 147 |
+
assert cse(e, optimizations=[(cse_opts.sub_pre, cse_opts.sub_post)]) == \
|
| 148 |
+
([], [0])
|
| 149 |
+
assert cse(((w + x + y + z)*(w - y - z))/(w + x)**3) == \
|
| 150 |
+
([(x0, w + x), (x1, y + z)], [(w - x1)*(x0 + x1)/x0**3])
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def test_multiple_expressions():
|
| 154 |
+
e1 = (x + y)*z
|
| 155 |
+
e2 = (x + y)*w
|
| 156 |
+
substs, reduced = cse([e1, e2])
|
| 157 |
+
assert substs == [(x0, x + y)]
|
| 158 |
+
assert reduced == [x0*z, x0*w]
|
| 159 |
+
l = [w*x*y + z, w*y]
|
| 160 |
+
substs, reduced = cse(l)
|
| 161 |
+
rsubsts, _ = cse(reversed(l))
|
| 162 |
+
assert substs == rsubsts
|
| 163 |
+
assert reduced == [z + x*x0, x0]
|
| 164 |
+
l = [w*x*y, w*x*y + z, w*y]
|
| 165 |
+
substs, reduced = cse(l)
|
| 166 |
+
rsubsts, _ = cse(reversed(l))
|
| 167 |
+
assert substs == rsubsts
|
| 168 |
+
assert reduced == [x1, x1 + z, x0]
|
| 169 |
+
l = [(x - z)*(y - z), x - z, y - z]
|
| 170 |
+
substs, reduced = cse(l)
|
| 171 |
+
rsubsts, _ = cse(reversed(l))
|
| 172 |
+
assert substs == [(x0, -z), (x1, x + x0), (x2, x0 + y)]
|
| 173 |
+
assert rsubsts == [(x0, -z), (x1, x0 + y), (x2, x + x0)]
|
| 174 |
+
assert reduced == [x1*x2, x1, x2]
|
| 175 |
+
l = [w*y + w + x + y + z, w*x*y]
|
| 176 |
+
assert cse(l) == ([(x0, w*y)], [w + x + x0 + y + z, x*x0])
|
| 177 |
+
assert cse([x + y, x + y + z]) == ([(x0, x + y)], [x0, z + x0])
|
| 178 |
+
assert cse([x + y, x + z]) == ([], [x + y, x + z])
|
| 179 |
+
assert cse([x*y, z + x*y, x*y*z + 3]) == \
|
| 180 |
+
([(x0, x*y)], [x0, z + x0, 3 + x0*z])
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
@XFAIL # CSE of non-commutative Mul terms is disabled
|
| 184 |
+
def test_non_commutative_cse():
|
| 185 |
+
A, B, C = symbols('A B C', commutative=False)
|
| 186 |
+
l = [A*B*C, A*C]
|
| 187 |
+
assert cse(l) == ([], l)
|
| 188 |
+
l = [A*B*C, A*B]
|
| 189 |
+
assert cse(l) == ([(x0, A*B)], [x0*C, x0])
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
# Test if CSE of non-commutative Mul terms is disabled
|
| 193 |
+
def test_bypass_non_commutatives():
|
| 194 |
+
A, B, C = symbols('A B C', commutative=False)
|
| 195 |
+
l = [A*B*C, A*C]
|
| 196 |
+
assert cse(l) == ([], l)
|
| 197 |
+
l = [A*B*C, A*B]
|
| 198 |
+
assert cse(l) == ([], l)
|
| 199 |
+
l = [B*C, A*B*C]
|
| 200 |
+
assert cse(l) == ([], l)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
@XFAIL # CSE fails when replacing non-commutative sub-expressions
|
| 204 |
+
def test_non_commutative_order():
|
| 205 |
+
A, B, C = symbols('A B C', commutative=False)
|
| 206 |
+
x0 = symbols('x0', commutative=False)
|
| 207 |
+
l = [B+C, A*(B+C)]
|
| 208 |
+
assert cse(l) == ([(x0, B+C)], [x0, A*x0])
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
@XFAIL # Worked in gh-11232, but was reverted due to performance considerations
|
| 212 |
+
def test_issue_10228():
|
| 213 |
+
assert cse([x*y**2 + x*y]) == ([(x0, x*y)], [x0*y + x0])
|
| 214 |
+
assert cse([x + y, 2*x + y]) == ([(x0, x + y)], [x0, x + x0])
|
| 215 |
+
assert cse((w + 2*x + y + z, w + x + 1)) == (
|
| 216 |
+
[(x0, w + x)], [x0 + x + y + z, x0 + 1])
|
| 217 |
+
assert cse(((w + x + y + z)*(w - x))/(w + x)) == (
|
| 218 |
+
[(x0, w + x)], [(x0 + y + z)*(w - x)/x0])
|
| 219 |
+
a, b, c, d, f, g, j, m = symbols('a, b, c, d, f, g, j, m')
|
| 220 |
+
exprs = (d*g**2*j*m, 4*a*f*g*m, a*b*c*f**2)
|
| 221 |
+
assert cse(exprs) == (
|
| 222 |
+
[(x0, g*m), (x1, a*f)], [d*g*j*x0, 4*x0*x1, b*c*f*x1]
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
@XFAIL
|
| 226 |
+
def test_powers():
|
| 227 |
+
assert cse(x*y**2 + x*y) == ([(x0, x*y)], [x0*y + x0])
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def test_issue_4498():
|
| 231 |
+
assert cse(w/(x - y) + z/(y - x), optimizations='basic') == \
|
| 232 |
+
([], [(w - z)/(x - y)])
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def test_issue_4020():
|
| 236 |
+
assert cse(x**5 + x**4 + x**3 + x**2, optimizations='basic') \
|
| 237 |
+
== ([(x0, x**2)], [x0*(x**3 + x + x0 + 1)])
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
def test_issue_4203():
|
| 241 |
+
assert cse(sin(x**x)/x**x) == ([(x0, x**x)], [sin(x0)/x0])
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def test_issue_6263():
|
| 245 |
+
e = Eq(x*(-x + 1) + x*(x - 1), 0)
|
| 246 |
+
assert cse(e, optimizations='basic') == ([], [True])
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def test_issue_25043():
|
| 250 |
+
c = symbols("c")
|
| 251 |
+
x = symbols("x0", real=True)
|
| 252 |
+
cse_expr = cse(c*x**2 + c*(x**4 - x**2))[-1][-1]
|
| 253 |
+
free = cse_expr.free_symbols
|
| 254 |
+
assert len(free) == len({i.name for i in free})
|
| 255 |
+
|
| 256 |
+
|
| 257 |
+
def test_dont_cse_tuples():
|
| 258 |
+
from sympy.core.function import Subs
|
| 259 |
+
f = Function("f")
|
| 260 |
+
g = Function("g")
|
| 261 |
+
|
| 262 |
+
name_val, (expr,) = cse(
|
| 263 |
+
Subs(f(x, y), (x, y), (0, 1))
|
| 264 |
+
+ Subs(g(x, y), (x, y), (0, 1)))
|
| 265 |
+
|
| 266 |
+
assert name_val == []
|
| 267 |
+
assert expr == (Subs(f(x, y), (x, y), (0, 1))
|
| 268 |
+
+ Subs(g(x, y), (x, y), (0, 1)))
|
| 269 |
+
|
| 270 |
+
name_val, (expr,) = cse(
|
| 271 |
+
Subs(f(x, y), (x, y), (0, x + y))
|
| 272 |
+
+ Subs(g(x, y), (x, y), (0, x + y)))
|
| 273 |
+
|
| 274 |
+
assert name_val == [(x0, x + y)]
|
| 275 |
+
assert expr == Subs(f(x, y), (x, y), (0, x0)) + \
|
| 276 |
+
Subs(g(x, y), (x, y), (0, x0))
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
def test_pow_invpow():
|
| 280 |
+
assert cse(1/x**2 + x**2) == \
|
| 281 |
+
([(x0, x**2)], [x0 + 1/x0])
|
| 282 |
+
assert cse(x**2 + (1 + 1/x**2)/x**2) == \
|
| 283 |
+
([(x0, x**2), (x1, 1/x0)], [x0 + x1*(x1 + 1)])
|
| 284 |
+
assert cse(1/x**2 + (1 + 1/x**2)*x**2) == \
|
| 285 |
+
([(x0, x**2), (x1, 1/x0)], [x0*(x1 + 1) + x1])
|
| 286 |
+
assert cse(cos(1/x**2) + sin(1/x**2)) == \
|
| 287 |
+
([(x0, x**(-2))], [sin(x0) + cos(x0)])
|
| 288 |
+
assert cse(cos(x**2) + sin(x**2)) == \
|
| 289 |
+
([(x0, x**2)], [sin(x0) + cos(x0)])
|
| 290 |
+
assert cse(y/(2 + x**2) + z/x**2/y) == \
|
| 291 |
+
([(x0, x**2)], [y/(x0 + 2) + z/(x0*y)])
|
| 292 |
+
assert cse(exp(x**2) + x**2*cos(1/x**2)) == \
|
| 293 |
+
([(x0, x**2)], [x0*cos(1/x0) + exp(x0)])
|
| 294 |
+
assert cse((1 + 1/x**2)/x**2) == \
|
| 295 |
+
([(x0, x**(-2))], [x0*(x0 + 1)])
|
| 296 |
+
assert cse(x**(2*y) + x**(-2*y)) == \
|
| 297 |
+
([(x0, x**(2*y))], [x0 + 1/x0])
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def test_postprocess():
|
| 301 |
+
eq = (x + 1 + exp((x + 1)/(y + 1)) + cos(y + 1))
|
| 302 |
+
assert cse([eq, Eq(x, z + 1), z - 2, (z + 1)*(x + 1)],
|
| 303 |
+
postprocess=cse_main.cse_separate) == \
|
| 304 |
+
[[(x0, y + 1), (x2, z + 1), (x, x2), (x1, x + 1)],
|
| 305 |
+
[x1 + exp(x1/x0) + cos(x0), z - 2, x1*x2]]
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def test_issue_4499():
|
| 309 |
+
# previously, this gave 16 constants
|
| 310 |
+
from sympy.abc import a, b
|
| 311 |
+
B = Function('B')
|
| 312 |
+
G = Function('G')
|
| 313 |
+
t = Tuple(*
|
| 314 |
+
(a, a + S.Half, 2*a, b, 2*a - b + 1, (sqrt(z)/2)**(-2*a + 1)*B(2*a -
|
| 315 |
+
b, sqrt(z))*B(b - 1, sqrt(z))*G(b)*G(2*a - b + 1),
|
| 316 |
+
sqrt(z)*(sqrt(z)/2)**(-2*a + 1)*B(b, sqrt(z))*B(2*a - b,
|
| 317 |
+
sqrt(z))*G(b)*G(2*a - b + 1), sqrt(z)*(sqrt(z)/2)**(-2*a + 1)*B(b - 1,
|
| 318 |
+
sqrt(z))*B(2*a - b + 1, sqrt(z))*G(b)*G(2*a - b + 1),
|
| 319 |
+
(sqrt(z)/2)**(-2*a + 1)*B(b, sqrt(z))*B(2*a - b + 1,
|
| 320 |
+
sqrt(z))*G(b)*G(2*a - b + 1), 1, 0, S.Half, z/2, -b + 1, -2*a + b,
|
| 321 |
+
-2*a))
|
| 322 |
+
c = cse(t)
|
| 323 |
+
ans = (
|
| 324 |
+
[(x0, 2*a), (x1, -b + x0), (x2, x1 + 1), (x3, b - 1), (x4, sqrt(z)),
|
| 325 |
+
(x5, B(x3, x4)), (x6, (x4/2)**(1 - x0)*G(b)*G(x2)), (x7, x6*B(x1, x4)),
|
| 326 |
+
(x8, B(b, x4)), (x9, x6*B(x2, x4))],
|
| 327 |
+
[(a, a + S.Half, x0, b, x2, x5*x7, x4*x7*x8, x4*x5*x9, x8*x9,
|
| 328 |
+
1, 0, S.Half, z/2, -x3, -x1, -x0)])
|
| 329 |
+
assert ans == c
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def test_issue_6169():
|
| 333 |
+
r = CRootOf(x**6 - 4*x**5 - 2, 1)
|
| 334 |
+
assert cse(r) == ([], [r])
|
| 335 |
+
# and a check that the right thing is done with the new
|
| 336 |
+
# mechanism
|
| 337 |
+
assert sub_post(sub_pre((-x - y)*z - x - y)) == -z*(x + y) - x - y
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
def test_cse_Indexed():
|
| 341 |
+
len_y = 5
|
| 342 |
+
y = IndexedBase('y', shape=(len_y,))
|
| 343 |
+
x = IndexedBase('x', shape=(len_y,))
|
| 344 |
+
i = Idx('i', len_y-1)
|
| 345 |
+
|
| 346 |
+
expr1 = (y[i+1]-y[i])/(x[i+1]-x[i])
|
| 347 |
+
expr2 = 1/(x[i+1]-x[i])
|
| 348 |
+
replacements, reduced_exprs = cse([expr1, expr2])
|
| 349 |
+
assert len(replacements) > 0
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
def test_cse_MatrixSymbol():
|
| 353 |
+
# MatrixSymbols have non-Basic args, so make sure that works
|
| 354 |
+
A = MatrixSymbol("A", 3, 3)
|
| 355 |
+
assert cse(A) == ([], [A])
|
| 356 |
+
|
| 357 |
+
n = symbols('n', integer=True)
|
| 358 |
+
B = MatrixSymbol("B", n, n)
|
| 359 |
+
assert cse(B) == ([], [B])
|
| 360 |
+
|
| 361 |
+
assert cse(A[0] * A[0]) == ([], [A[0]*A[0]])
|
| 362 |
+
|
| 363 |
+
assert cse(A[0,0]*A[0,1] + A[0,0]*A[0,1]*A[0,2]) == ([(x0, A[0, 0]*A[0, 1])], [x0*A[0, 2] + x0])
|
| 364 |
+
|
| 365 |
+
def test_cse_MatrixExpr():
|
| 366 |
+
A = MatrixSymbol('A', 3, 3)
|
| 367 |
+
y = MatrixSymbol('y', 3, 1)
|
| 368 |
+
|
| 369 |
+
expr1 = (A.T*A).I * A * y
|
| 370 |
+
expr2 = (A.T*A) * A * y
|
| 371 |
+
replacements, reduced_exprs = cse([expr1, expr2])
|
| 372 |
+
assert len(replacements) > 0
|
| 373 |
+
|
| 374 |
+
replacements, reduced_exprs = cse([expr1 + expr2, expr1])
|
| 375 |
+
assert replacements
|
| 376 |
+
|
| 377 |
+
replacements, reduced_exprs = cse([A**2, A + A**2])
|
| 378 |
+
assert replacements
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
def test_Piecewise():
|
| 382 |
+
f = Piecewise((-z + x*y, Eq(y, 0)), (-z - x*y, True))
|
| 383 |
+
ans = cse(f)
|
| 384 |
+
actual_ans = ([(x0, x*y)],
|
| 385 |
+
[Piecewise((x0 - z, Eq(y, 0)), (-z - x0, True))])
|
| 386 |
+
assert ans == actual_ans
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
def test_ignore_order_terms():
|
| 390 |
+
eq = exp(x).series(x,0,3) + sin(y+x**3) - 1
|
| 391 |
+
assert cse(eq) == ([], [sin(x**3 + y) + x + x**2/2 + O(x**3)])
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def test_name_conflict():
|
| 395 |
+
z1 = x0 + y
|
| 396 |
+
z2 = x2 + x3
|
| 397 |
+
l = [cos(z1) + z1, cos(z2) + z2, x0 + x2]
|
| 398 |
+
substs, reduced = cse(l)
|
| 399 |
+
assert [e.subs(reversed(substs)) for e in reduced] == l
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
def test_name_conflict_cust_symbols():
|
| 403 |
+
z1 = x0 + y
|
| 404 |
+
z2 = x2 + x3
|
| 405 |
+
l = [cos(z1) + z1, cos(z2) + z2, x0 + x2]
|
| 406 |
+
substs, reduced = cse(l, symbols("x:10"))
|
| 407 |
+
assert [e.subs(reversed(substs)) for e in reduced] == l
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
def test_symbols_exhausted_error():
|
| 411 |
+
l = cos(x+y)+x+y+cos(w+y)+sin(w+y)
|
| 412 |
+
sym = [x, y, z]
|
| 413 |
+
with raises(ValueError):
|
| 414 |
+
cse(l, symbols=sym)
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def test_issue_7840():
|
| 418 |
+
# daveknippers' example
|
| 419 |
+
C393 = sympify( \
|
| 420 |
+
'Piecewise((C391 - 1.65, C390 < 0.5), (Piecewise((C391 - 1.65, \
|
| 421 |
+
C391 > 2.35), (C392, True)), True))'
|
| 422 |
+
)
|
| 423 |
+
C391 = sympify( \
|
| 424 |
+
'Piecewise((2.05*C390**(-1.03), C390 < 0.5), (2.5*C390**(-0.625), True))'
|
| 425 |
+
)
|
| 426 |
+
C393 = C393.subs('C391',C391)
|
| 427 |
+
# simple substitution
|
| 428 |
+
sub = {}
|
| 429 |
+
sub['C390'] = 0.703451854
|
| 430 |
+
sub['C392'] = 1.01417794
|
| 431 |
+
ss_answer = C393.subs(sub)
|
| 432 |
+
# cse
|
| 433 |
+
substitutions,new_eqn = cse(C393)
|
| 434 |
+
for pair in substitutions:
|
| 435 |
+
sub[pair[0].name] = pair[1].subs(sub)
|
| 436 |
+
cse_answer = new_eqn[0].subs(sub)
|
| 437 |
+
# both methods should be the same
|
| 438 |
+
assert ss_answer == cse_answer
|
| 439 |
+
|
| 440 |
+
# GitRay's example
|
| 441 |
+
expr = sympify(
|
| 442 |
+
"Piecewise((Symbol('ON'), Equality(Symbol('mode'), Symbol('ON'))), \
|
| 443 |
+
(Piecewise((Piecewise((Symbol('OFF'), StrictLessThan(Symbol('x'), \
|
| 444 |
+
Symbol('threshold'))), (Symbol('ON'), true)), Equality(Symbol('mode'), \
|
| 445 |
+
Symbol('AUTO'))), (Symbol('OFF'), true)), true))"
|
| 446 |
+
)
|
| 447 |
+
substitutions, new_eqn = cse(expr)
|
| 448 |
+
# this Piecewise should be exactly the same
|
| 449 |
+
assert new_eqn[0] == expr
|
| 450 |
+
# there should not be any replacements
|
| 451 |
+
assert len(substitutions) < 1
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
def test_issue_8891():
|
| 455 |
+
for cls in (MutableDenseMatrix, MutableSparseMatrix,
|
| 456 |
+
ImmutableDenseMatrix, ImmutableSparseMatrix):
|
| 457 |
+
m = cls(2, 2, [x + y, 0, 0, 0])
|
| 458 |
+
res = cse([x + y, m])
|
| 459 |
+
ans = ([(x0, x + y)], [x0, cls([[x0, 0], [0, 0]])])
|
| 460 |
+
assert res == ans
|
| 461 |
+
assert isinstance(res[1][-1], cls)
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
def test_issue_11230():
|
| 465 |
+
# a specific test that always failed
|
| 466 |
+
a, b, f, k, l, i = symbols('a b f k l i')
|
| 467 |
+
p = [a*b*f*k*l, a*i*k**2*l, f*i*k**2*l]
|
| 468 |
+
R, C = cse(p)
|
| 469 |
+
assert not any(i.is_Mul for a in C for i in a.args)
|
| 470 |
+
|
| 471 |
+
# random tests for the issue
|
| 472 |
+
from sympy.core.random import choice
|
| 473 |
+
from sympy.core.function import expand_mul
|
| 474 |
+
s = symbols('a:m')
|
| 475 |
+
# 35 Mul tests, none of which should ever fail
|
| 476 |
+
ex = [Mul(*[choice(s) for i in range(5)]) for i in range(7)]
|
| 477 |
+
for p in subsets(ex, 3):
|
| 478 |
+
p = list(p)
|
| 479 |
+
R, C = cse(p)
|
| 480 |
+
assert not any(i.is_Mul for a in C for i in a.args)
|
| 481 |
+
for ri in reversed(R):
|
| 482 |
+
for i in range(len(C)):
|
| 483 |
+
C[i] = C[i].subs(*ri)
|
| 484 |
+
assert p == C
|
| 485 |
+
# 35 Add tests, none of which should ever fail
|
| 486 |
+
ex = [Add(*[choice(s[:7]) for i in range(5)]) for i in range(7)]
|
| 487 |
+
for p in subsets(ex, 3):
|
| 488 |
+
p = list(p)
|
| 489 |
+
R, C = cse(p)
|
| 490 |
+
assert not any(i.is_Add for a in C for i in a.args)
|
| 491 |
+
for ri in reversed(R):
|
| 492 |
+
for i in range(len(C)):
|
| 493 |
+
C[i] = C[i].subs(*ri)
|
| 494 |
+
# use expand_mul to handle cases like this:
|
| 495 |
+
# p = [a + 2*b + 2*e, 2*b + c + 2*e, b + 2*c + 2*g]
|
| 496 |
+
# x0 = 2*(b + e) is identified giving a rebuilt p that
|
| 497 |
+
# is now `[a + 2*(b + e), c + 2*(b + e), b + 2*c + 2*g]`
|
| 498 |
+
assert p == [expand_mul(i) for i in C]
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
@XFAIL
|
| 502 |
+
def test_issue_11577():
|
| 503 |
+
def check(eq):
|
| 504 |
+
r, c = cse(eq)
|
| 505 |
+
assert eq.count_ops() >= \
|
| 506 |
+
len(r) + sum(i[1].count_ops() for i in r) + \
|
| 507 |
+
count_ops(c)
|
| 508 |
+
|
| 509 |
+
eq = x**5*y**2 + x**5*y + x**5
|
| 510 |
+
assert cse(eq) == (
|
| 511 |
+
[(x0, x**4), (x1, x*y)], [x**5 + x0*x1*y + x0*x1])
|
| 512 |
+
# ([(x0, x**5*y)], [x0*y + x0 + x**5]) or
|
| 513 |
+
# ([(x0, x**5)], [x0*y**2 + x0*y + x0])
|
| 514 |
+
check(eq)
|
| 515 |
+
|
| 516 |
+
eq = x**2/(y + 1)**2 + x/(y + 1)
|
| 517 |
+
assert cse(eq) == (
|
| 518 |
+
[(x0, y + 1)], [x**2/x0**2 + x/x0])
|
| 519 |
+
# ([(x0, x/(y + 1))], [x0**2 + x0])
|
| 520 |
+
check(eq)
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
def test_hollow_rejection():
|
| 524 |
+
eq = [x + 3, x + 4]
|
| 525 |
+
assert cse(eq) == ([], eq)
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
def test_cse_ignore():
|
| 529 |
+
exprs = [exp(y)*(3*y + 3*sqrt(x+1)), exp(y)*(5*y + 5*sqrt(x+1))]
|
| 530 |
+
subst1, red1 = cse(exprs)
|
| 531 |
+
assert any(y in sub.free_symbols for _, sub in subst1), "cse failed to identify any term with y"
|
| 532 |
+
|
| 533 |
+
subst2, red2 = cse(exprs, ignore=(y,)) # y is not allowed in substitutions
|
| 534 |
+
assert not any(y in sub.free_symbols for _, sub in subst2), "Sub-expressions containing y must be ignored"
|
| 535 |
+
assert any(sub - sqrt(x + 1) == 0 for _, sub in subst2), "cse failed to identify sqrt(x + 1) as sub-expression"
|
| 536 |
+
|
| 537 |
+
def test_cse_ignore_issue_15002():
|
| 538 |
+
l = [
|
| 539 |
+
w*exp(x)*exp(-z),
|
| 540 |
+
exp(y)*exp(x)*exp(-z)
|
| 541 |
+
]
|
| 542 |
+
substs, reduced = cse(l, ignore=(x,))
|
| 543 |
+
rl = [e.subs(reversed(substs)) for e in reduced]
|
| 544 |
+
assert rl == l
|
| 545 |
+
|
| 546 |
+
|
| 547 |
+
def test_cse_unevaluated():
|
| 548 |
+
xp1 = UnevaluatedExpr(x + 1)
|
| 549 |
+
# This used to cause RecursionError
|
| 550 |
+
[(x0, ue)], [red] = cse([(-1 - xp1) / (1 - xp1)])
|
| 551 |
+
if ue == xp1:
|
| 552 |
+
assert red == (-1 - x0) / (1 - x0)
|
| 553 |
+
elif ue == -xp1:
|
| 554 |
+
assert red == (-1 + x0) / (1 + x0)
|
| 555 |
+
else:
|
| 556 |
+
msg = f'Expected common subexpression {xp1} or {-xp1}, instead got {ue}'
|
| 557 |
+
assert False, msg
|
| 558 |
+
|
| 559 |
+
|
| 560 |
+
def test_cse__performance():
|
| 561 |
+
nexprs, nterms = 3, 20
|
| 562 |
+
x = symbols('x:%d' % nterms)
|
| 563 |
+
exprs = [
|
| 564 |
+
reduce(add, [x[j]*(-1)**(i+j) for j in range(nterms)])
|
| 565 |
+
for i in range(nexprs)
|
| 566 |
+
]
|
| 567 |
+
assert (exprs[0] + exprs[1]).simplify() == 0
|
| 568 |
+
subst, red = cse(exprs)
|
| 569 |
+
assert len(subst) > 0, "exprs[0] == -exprs[2], i.e. a CSE"
|
| 570 |
+
for i, e in enumerate(red):
|
| 571 |
+
assert (e.subs(reversed(subst)) - exprs[i]).simplify() == 0
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
def test_issue_12070():
|
| 575 |
+
exprs = [x + y, 2 + x + y, x + y + z, 3 + x + y + z]
|
| 576 |
+
subst, red = cse(exprs)
|
| 577 |
+
assert 6 >= (len(subst) + sum(v.count_ops() for k, v in subst) +
|
| 578 |
+
count_ops(red))
|
| 579 |
+
|
| 580 |
+
|
| 581 |
+
def test_issue_13000():
|
| 582 |
+
eq = x/(-4*x**2 + y**2)
|
| 583 |
+
cse_eq = cse(eq)[1][0]
|
| 584 |
+
assert cse_eq == eq
|
| 585 |
+
|
| 586 |
+
|
| 587 |
+
def test_issue_18203():
|
| 588 |
+
eq = CRootOf(x**5 + 11*x - 2, 0) + CRootOf(x**5 + 11*x - 2, 1)
|
| 589 |
+
assert cse(eq) == ([], [eq])
|
| 590 |
+
|
| 591 |
+
|
| 592 |
+
def test_unevaluated_mul():
|
| 593 |
+
eq = Mul(x + y, x + y, evaluate=False)
|
| 594 |
+
assert cse(eq) == ([(x0, x + y)], [x0**2])
|
| 595 |
+
|
| 596 |
+
def test_cse_release_variables():
|
| 597 |
+
from sympy.simplify.cse_main import cse_release_variables
|
| 598 |
+
_0, _1, _2, _3, _4 = symbols('_:5')
|
| 599 |
+
eqs = [(x + y - 1)**2, x,
|
| 600 |
+
x + y, (x + y)/(2*x + 1) + (x + y - 1)**2,
|
| 601 |
+
(2*x + 1)**(x + y)]
|
| 602 |
+
r, e = cse(eqs, postprocess=cse_release_variables)
|
| 603 |
+
# this can change in keeping with the intention of the function
|
| 604 |
+
assert r, e == ([
|
| 605 |
+
(x0, x + y), (x1, (x0 - 1)**2), (x2, 2*x + 1),
|
| 606 |
+
(_3, x0/x2 + x1), (_4, x2**x0), (x2, None), (_0, x1),
|
| 607 |
+
(x1, None), (_2, x0), (x0, None), (_1, x)], (_0, _1, _2, _3, _4))
|
| 608 |
+
r.reverse()
|
| 609 |
+
r = [(s, v) for s, v in r if v is not None]
|
| 610 |
+
assert eqs == [i.subs(r) for i in e]
|
| 611 |
+
|
| 612 |
+
def test_cse_list():
|
| 613 |
+
_cse = lambda x: cse(x, list=False)
|
| 614 |
+
assert _cse(x) == ([], x)
|
| 615 |
+
assert _cse('x') == ([], 'x')
|
| 616 |
+
it = [x]
|
| 617 |
+
for c in (list, tuple, set):
|
| 618 |
+
assert _cse(c(it)) == ([], c(it))
|
| 619 |
+
#Tuple works different from tuple:
|
| 620 |
+
assert _cse(Tuple(*it)) == ([], Tuple(*it))
|
| 621 |
+
d = {x: 1}
|
| 622 |
+
assert _cse(d) == ([], d)
|
| 623 |
+
|
| 624 |
+
def test_issue_18991():
|
| 625 |
+
A = MatrixSymbol('A', 2, 2)
|
| 626 |
+
assert signsimp(-A * A - A) == -A * A - A
|
| 627 |
+
|
| 628 |
+
|
| 629 |
+
def test_unevaluated_Mul():
|
| 630 |
+
m = [Mul(1, 2, evaluate=False)]
|
| 631 |
+
assert cse(m) == ([], m)
|
| 632 |
+
|
| 633 |
+
|
| 634 |
+
def test_cse_matrix_expression_inverse():
|
| 635 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 636 |
+
x = Inverse(A)
|
| 637 |
+
cse_expr = cse(x)
|
| 638 |
+
assert cse_expr == ([], [Inverse(A)])
|
| 639 |
+
|
| 640 |
+
|
| 641 |
+
def test_cse_matrix_expression_matmul_inverse():
|
| 642 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 643 |
+
b = ImmutableDenseMatrix(symbols('b:2'))
|
| 644 |
+
x = MatMul(Inverse(A), b)
|
| 645 |
+
cse_expr = cse(x)
|
| 646 |
+
assert cse_expr == ([], [x])
|
| 647 |
+
|
| 648 |
+
|
| 649 |
+
def test_cse_matrix_negate_matrix():
|
| 650 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 651 |
+
x = MatMul(S.NegativeOne, A)
|
| 652 |
+
cse_expr = cse(x)
|
| 653 |
+
assert cse_expr == ([], [x])
|
| 654 |
+
|
| 655 |
+
|
| 656 |
+
def test_cse_matrix_negate_matmul_not_extracted():
|
| 657 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 658 |
+
B = ImmutableDenseMatrix(symbols('B:4')).reshape(2, 2)
|
| 659 |
+
x = MatMul(S.NegativeOne, A, B)
|
| 660 |
+
cse_expr = cse(x)
|
| 661 |
+
assert cse_expr == ([], [x])
|
| 662 |
+
|
| 663 |
+
|
| 664 |
+
@XFAIL # No simplification rule for nested associative operations
|
| 665 |
+
def test_cse_matrix_nested_matmul_collapsed():
|
| 666 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 667 |
+
B = ImmutableDenseMatrix(symbols('B:4')).reshape(2, 2)
|
| 668 |
+
x = MatMul(S.NegativeOne, MatMul(A, B))
|
| 669 |
+
cse_expr = cse(x)
|
| 670 |
+
assert cse_expr == ([], [MatMul(S.NegativeOne, A, B)])
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def test_cse_matrix_optimize_out_single_argument_mul():
|
| 674 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 675 |
+
x = MatMul(MatMul(MatMul(A)))
|
| 676 |
+
cse_expr = cse(x)
|
| 677 |
+
assert cse_expr == ([], [A])
|
| 678 |
+
|
| 679 |
+
|
| 680 |
+
@XFAIL # Multiple simplification passed not supported in CSE
|
| 681 |
+
def test_cse_matrix_optimize_out_single_argument_mul_combined():
|
| 682 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 683 |
+
x = MatAdd(MatMul(MatMul(MatMul(A))), MatMul(MatMul(A)), MatMul(A), A)
|
| 684 |
+
cse_expr = cse(x)
|
| 685 |
+
assert cse_expr == ([], [MatMul(4, A)])
|
| 686 |
+
|
| 687 |
+
|
| 688 |
+
def test_cse_matrix_optimize_out_single_argument_add():
|
| 689 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 690 |
+
x = MatAdd(MatAdd(MatAdd(MatAdd(A))))
|
| 691 |
+
cse_expr = cse(x)
|
| 692 |
+
assert cse_expr == ([], [A])
|
| 693 |
+
|
| 694 |
+
|
| 695 |
+
@XFAIL # Multiple simplification passed not supported in CSE
|
| 696 |
+
def test_cse_matrix_optimize_out_single_argument_add_combined():
|
| 697 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 698 |
+
x = MatMul(MatAdd(MatAdd(MatAdd(A))), MatAdd(MatAdd(A)), MatAdd(A), A)
|
| 699 |
+
cse_expr = cse(x)
|
| 700 |
+
assert cse_expr == ([], [MatMul(4, A)])
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
def test_cse_matrix_expression_matrix_solve():
|
| 704 |
+
A = ImmutableDenseMatrix(symbols('A:4')).reshape(2, 2)
|
| 705 |
+
b = ImmutableDenseMatrix(symbols('b:2'))
|
| 706 |
+
x = MatrixSolve(A, b)
|
| 707 |
+
cse_expr = cse(x)
|
| 708 |
+
assert cse_expr == ([], [x])
|
| 709 |
+
|
| 710 |
+
|
| 711 |
+
def test_cse_matrix_matrix_expression():
|
| 712 |
+
X = ImmutableDenseMatrix(symbols('X:4')).reshape(2, 2)
|
| 713 |
+
y = ImmutableDenseMatrix(symbols('y:2'))
|
| 714 |
+
b = MatMul(Inverse(MatMul(Transpose(X), X)), Transpose(X), y)
|
| 715 |
+
cse_expr = cse(b)
|
| 716 |
+
x0 = MatrixSymbol('x0', 2, 2)
|
| 717 |
+
reduced_expr_expected = MatMul(Inverse(MatMul(x0, X)), x0, y)
|
| 718 |
+
assert cse_expr == ([(x0, Transpose(X))], [reduced_expr_expected])
|
| 719 |
+
|
| 720 |
+
|
| 721 |
+
def test_cse_matrix_kalman_filter():
|
| 722 |
+
"""Kalman Filter example from Matthew Rocklin's SciPy 2013 talk.
|
| 723 |
+
|
| 724 |
+
Talk titled: "Matrix Expressions and BLAS/LAPACK; SciPy 2013 Presentation"
|
| 725 |
+
|
| 726 |
+
Video: https://pyvideo.org/scipy-2013/matrix-expressions-and-blaslapack-scipy-2013-pr.html
|
| 727 |
+
|
| 728 |
+
Notes
|
| 729 |
+
=====
|
| 730 |
+
|
| 731 |
+
Equations are:
|
| 732 |
+
|
| 733 |
+
new_mu = mu + Sigma*H.T * (R + H*Sigma*H.T).I * (H*mu - data)
|
| 734 |
+
= MatAdd(mu, MatMul(Sigma, Transpose(H), Inverse(MatAdd(R, MatMul(H, Sigma, Transpose(H)))), MatAdd(MatMul(H, mu), MatMul(S.NegativeOne, data))))
|
| 735 |
+
new_Sigma = Sigma - Sigma*H.T * (R + H*Sigma*H.T).I * H * Sigma
|
| 736 |
+
= MatAdd(Sigma, MatMul(S.NegativeOne, Sigma, Transpose(H)), Inverse(MatAdd(R, MatMul(H*Sigma*Transpose(H)))), H, Sigma))
|
| 737 |
+
|
| 738 |
+
"""
|
| 739 |
+
N = 2
|
| 740 |
+
mu = ImmutableDenseMatrix(symbols(f'mu:{N}'))
|
| 741 |
+
Sigma = ImmutableDenseMatrix(symbols(f'Sigma:{N * N}')).reshape(N, N)
|
| 742 |
+
H = ImmutableDenseMatrix(symbols(f'H:{N * N}')).reshape(N, N)
|
| 743 |
+
R = ImmutableDenseMatrix(symbols(f'R:{N * N}')).reshape(N, N)
|
| 744 |
+
data = ImmutableDenseMatrix(symbols(f'data:{N}'))
|
| 745 |
+
new_mu = MatAdd(mu, MatMul(Sigma, Transpose(H), Inverse(MatAdd(R, MatMul(H, Sigma, Transpose(H)))), MatAdd(MatMul(H, mu), MatMul(S.NegativeOne, data))))
|
| 746 |
+
new_Sigma = MatAdd(Sigma, MatMul(S.NegativeOne, Sigma, Transpose(H), Inverse(MatAdd(R, MatMul(H, Sigma, Transpose(H)))), H, Sigma))
|
| 747 |
+
cse_expr = cse([new_mu, new_Sigma])
|
| 748 |
+
x0 = MatrixSymbol('x0', N, N)
|
| 749 |
+
x1 = MatrixSymbol('x1', N, N)
|
| 750 |
+
replacements_expected = [
|
| 751 |
+
(x0, Transpose(H)),
|
| 752 |
+
(x1, Inverse(MatAdd(R, MatMul(H, Sigma, x0)))),
|
| 753 |
+
]
|
| 754 |
+
reduced_exprs_expected = [
|
| 755 |
+
MatAdd(mu, MatMul(Sigma, x0, x1, MatAdd(MatMul(H, mu), MatMul(S.NegativeOne, data)))),
|
| 756 |
+
MatAdd(Sigma, MatMul(S.NegativeOne, Sigma, x0, x1, H, Sigma)),
|
| 757 |
+
]
|
| 758 |
+
assert cse_expr == (replacements_expected, reduced_exprs_expected)
|
lib/python3.12/site-packages/sympy/simplify/tests/test_epathtools.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tests for tools for manipulation of expressions using paths. """
|
| 2 |
+
|
| 3 |
+
from sympy.simplify.epathtools import epath, EPath
|
| 4 |
+
from sympy.testing.pytest import raises
|
| 5 |
+
|
| 6 |
+
from sympy.core.numbers import E
|
| 7 |
+
from sympy.functions.elementary.trigonometric import (cos, sin)
|
| 8 |
+
from sympy.abc import x, y, z, t
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_epath_select():
|
| 12 |
+
expr = [((x, 1, t), 2), ((3, y, 4), z)]
|
| 13 |
+
|
| 14 |
+
assert epath("/*", expr) == [((x, 1, t), 2), ((3, y, 4), z)]
|
| 15 |
+
assert epath("/*/*", expr) == [(x, 1, t), 2, (3, y, 4), z]
|
| 16 |
+
assert epath("/*/*/*", expr) == [x, 1, t, 3, y, 4]
|
| 17 |
+
assert epath("/*/*/*/*", expr) == []
|
| 18 |
+
|
| 19 |
+
assert epath("/[:]", expr) == [((x, 1, t), 2), ((3, y, 4), z)]
|
| 20 |
+
assert epath("/[:]/[:]", expr) == [(x, 1, t), 2, (3, y, 4), z]
|
| 21 |
+
assert epath("/[:]/[:]/[:]", expr) == [x, 1, t, 3, y, 4]
|
| 22 |
+
assert epath("/[:]/[:]/[:]/[:]", expr) == []
|
| 23 |
+
|
| 24 |
+
assert epath("/*/[:]", expr) == [(x, 1, t), 2, (3, y, 4), z]
|
| 25 |
+
|
| 26 |
+
assert epath("/*/[0]", expr) == [(x, 1, t), (3, y, 4)]
|
| 27 |
+
assert epath("/*/[1]", expr) == [2, z]
|
| 28 |
+
assert epath("/*/[2]", expr) == []
|
| 29 |
+
|
| 30 |
+
assert epath("/*/int", expr) == [2]
|
| 31 |
+
assert epath("/*/Symbol", expr) == [z]
|
| 32 |
+
assert epath("/*/tuple", expr) == [(x, 1, t), (3, y, 4)]
|
| 33 |
+
assert epath("/*/__iter__?", expr) == [(x, 1, t), (3, y, 4)]
|
| 34 |
+
|
| 35 |
+
assert epath("/*/int|tuple", expr) == [(x, 1, t), 2, (3, y, 4)]
|
| 36 |
+
assert epath("/*/Symbol|tuple", expr) == [(x, 1, t), (3, y, 4), z]
|
| 37 |
+
assert epath("/*/int|Symbol|tuple", expr) == [(x, 1, t), 2, (3, y, 4), z]
|
| 38 |
+
|
| 39 |
+
assert epath("/*/int|__iter__?", expr) == [(x, 1, t), 2, (3, y, 4)]
|
| 40 |
+
assert epath("/*/Symbol|__iter__?", expr) == [(x, 1, t), (3, y, 4), z]
|
| 41 |
+
assert epath(
|
| 42 |
+
"/*/int|Symbol|__iter__?", expr) == [(x, 1, t), 2, (3, y, 4), z]
|
| 43 |
+
|
| 44 |
+
assert epath("/*/[0]/int", expr) == [1, 3, 4]
|
| 45 |
+
assert epath("/*/[0]/Symbol", expr) == [x, t, y]
|
| 46 |
+
|
| 47 |
+
assert epath("/*/[0]/int[1:]", expr) == [1, 4]
|
| 48 |
+
assert epath("/*/[0]/Symbol[1:]", expr) == [t, y]
|
| 49 |
+
|
| 50 |
+
assert epath("/Symbol", x + y + z + 1) == [x, y, z]
|
| 51 |
+
assert epath("/*/*/Symbol", t + sin(x + 1) + cos(x + y + E)) == [x, x, y]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def test_epath_apply():
|
| 55 |
+
expr = [((x, 1, t), 2), ((3, y, 4), z)]
|
| 56 |
+
func = lambda expr: expr**2
|
| 57 |
+
|
| 58 |
+
assert epath("/*", expr, list) == [[(x, 1, t), 2], [(3, y, 4), z]]
|
| 59 |
+
|
| 60 |
+
assert epath("/*/[0]", expr, list) == [([x, 1, t], 2), ([3, y, 4], z)]
|
| 61 |
+
assert epath("/*/[1]", expr, func) == [((x, 1, t), 4), ((3, y, 4), z**2)]
|
| 62 |
+
assert epath("/*/[2]", expr, list) == expr
|
| 63 |
+
|
| 64 |
+
assert epath("/*/[0]/int", expr, func) == [((x, 1, t), 2), ((9, y, 16), z)]
|
| 65 |
+
assert epath("/*/[0]/Symbol", expr, func) == [((x**2, 1, t**2), 2),
|
| 66 |
+
((3, y**2, 4), z)]
|
| 67 |
+
assert epath(
|
| 68 |
+
"/*/[0]/int[1:]", expr, func) == [((x, 1, t), 2), ((3, y, 16), z)]
|
| 69 |
+
assert epath("/*/[0]/Symbol[1:]", expr, func) == [((x, 1, t**2),
|
| 70 |
+
2), ((3, y**2, 4), z)]
|
| 71 |
+
|
| 72 |
+
assert epath("/Symbol", x + y + z + 1, func) == x**2 + y**2 + z**2 + 1
|
| 73 |
+
assert epath("/*/*/Symbol", t + sin(x + 1) + cos(x + y + E), func) == \
|
| 74 |
+
t + sin(x**2 + 1) + cos(x**2 + y**2 + E)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def test_EPath():
|
| 78 |
+
assert EPath("/*/[0]")._path == "/*/[0]"
|
| 79 |
+
assert EPath(EPath("/*/[0]"))._path == "/*/[0]"
|
| 80 |
+
assert isinstance(epath("/*/[0]"), EPath) is True
|
| 81 |
+
|
| 82 |
+
assert repr(EPath("/*/[0]")) == "EPath('/*/[0]')"
|
| 83 |
+
|
| 84 |
+
raises(ValueError, lambda: EPath(""))
|
| 85 |
+
raises(ValueError, lambda: EPath("/"))
|
| 86 |
+
raises(ValueError, lambda: EPath("/|x"))
|
| 87 |
+
raises(ValueError, lambda: EPath("/["))
|
| 88 |
+
raises(ValueError, lambda: EPath("/[0]%"))
|
| 89 |
+
|
| 90 |
+
raises(NotImplementedError, lambda: EPath("Symbol"))
|
lib/python3.12/site-packages/sympy/simplify/tests/test_fu.py
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.add import Add
|
| 2 |
+
from sympy.core.mul import Mul
|
| 3 |
+
from sympy.core.numbers import (I, Rational, pi)
|
| 4 |
+
from sympy.core.parameters import evaluate
|
| 5 |
+
from sympy.core.singleton import S
|
| 6 |
+
from sympy.core.symbol import (Dummy, Symbol, symbols)
|
| 7 |
+
from sympy.functions.elementary.hyperbolic import (cosh, coth, csch, sech, sinh, tanh)
|
| 8 |
+
from sympy.functions.elementary.miscellaneous import (root, sqrt)
|
| 9 |
+
from sympy.functions.elementary.trigonometric import (cos, cot, csc, sec, sin, tan)
|
| 10 |
+
from sympy.simplify.powsimp import powsimp
|
| 11 |
+
from sympy.simplify.fu import (
|
| 12 |
+
L, TR1, TR10, TR10i, TR11, _TR11, TR12, TR12i, TR13, TR14, TR15, TR16,
|
| 13 |
+
TR111, TR2, TR2i, TR3, TR4, TR5, TR6, TR7, TR8, TR9, TRmorrie, _TR56 as T,
|
| 14 |
+
TRpower, hyper_as_trig, fu, process_common_addends, trig_split,
|
| 15 |
+
as_f_sign_1)
|
| 16 |
+
from sympy.core.random import verify_numerically
|
| 17 |
+
from sympy.abc import a, b, c, x, y, z
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_TR1():
|
| 21 |
+
assert TR1(2*csc(x) + sec(x)) == 1/cos(x) + 2/sin(x)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def test_TR2():
|
| 25 |
+
assert TR2(tan(x)) == sin(x)/cos(x)
|
| 26 |
+
assert TR2(cot(x)) == cos(x)/sin(x)
|
| 27 |
+
assert TR2(tan(tan(x) - sin(x)/cos(x))) == 0
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def test_TR2i():
|
| 31 |
+
# just a reminder that ratios of powers only simplify if both
|
| 32 |
+
# numerator and denominator satisfy the condition that each
|
| 33 |
+
# has a positive base or an integer exponent; e.g. the following,
|
| 34 |
+
# at y=-1, x=1/2 gives sqrt(2)*I != -sqrt(2)*I
|
| 35 |
+
assert powsimp(2**x/y**x) != (2/y)**x
|
| 36 |
+
|
| 37 |
+
assert TR2i(sin(x)/cos(x)) == tan(x)
|
| 38 |
+
assert TR2i(sin(x)*sin(y)/cos(x)) == tan(x)*sin(y)
|
| 39 |
+
assert TR2i(1/(sin(x)/cos(x))) == 1/tan(x)
|
| 40 |
+
assert TR2i(1/(sin(x)*sin(y)/cos(x))) == 1/tan(x)/sin(y)
|
| 41 |
+
assert TR2i(sin(x)/2/(cos(x) + 1)) == sin(x)/(cos(x) + 1)/2
|
| 42 |
+
|
| 43 |
+
assert TR2i(sin(x)/2/(cos(x) + 1), half=True) == tan(x/2)/2
|
| 44 |
+
assert TR2i(sin(1)/(cos(1) + 1), half=True) == tan(S.Half)
|
| 45 |
+
assert TR2i(sin(2)/(cos(2) + 1), half=True) == tan(1)
|
| 46 |
+
assert TR2i(sin(4)/(cos(4) + 1), half=True) == tan(2)
|
| 47 |
+
assert TR2i(sin(5)/(cos(5) + 1), half=True) == tan(5*S.Half)
|
| 48 |
+
assert TR2i((cos(1) + 1)/sin(1), half=True) == 1/tan(S.Half)
|
| 49 |
+
assert TR2i((cos(2) + 1)/sin(2), half=True) == 1/tan(1)
|
| 50 |
+
assert TR2i((cos(4) + 1)/sin(4), half=True) == 1/tan(2)
|
| 51 |
+
assert TR2i((cos(5) + 1)/sin(5), half=True) == 1/tan(5*S.Half)
|
| 52 |
+
assert TR2i((cos(1) + 1)**(-a)*sin(1)**a, half=True) == tan(S.Half)**a
|
| 53 |
+
assert TR2i((cos(2) + 1)**(-a)*sin(2)**a, half=True) == tan(1)**a
|
| 54 |
+
assert TR2i((cos(4) + 1)**(-a)*sin(4)**a, half=True) == (cos(4) + 1)**(-a)*sin(4)**a
|
| 55 |
+
assert TR2i((cos(5) + 1)**(-a)*sin(5)**a, half=True) == (cos(5) + 1)**(-a)*sin(5)**a
|
| 56 |
+
assert TR2i((cos(1) + 1)**a*sin(1)**(-a), half=True) == tan(S.Half)**(-a)
|
| 57 |
+
assert TR2i((cos(2) + 1)**a*sin(2)**(-a), half=True) == tan(1)**(-a)
|
| 58 |
+
assert TR2i((cos(4) + 1)**a*sin(4)**(-a), half=True) == (cos(4) + 1)**a*sin(4)**(-a)
|
| 59 |
+
assert TR2i((cos(5) + 1)**a*sin(5)**(-a), half=True) == (cos(5) + 1)**a*sin(5)**(-a)
|
| 60 |
+
|
| 61 |
+
i = symbols('i', integer=True)
|
| 62 |
+
assert TR2i(((cos(5) + 1)**i*sin(5)**(-i)), half=True) == tan(5*S.Half)**(-i)
|
| 63 |
+
assert TR2i(1/((cos(5) + 1)**i*sin(5)**(-i)), half=True) == tan(5*S.Half)**i
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def test_TR3():
|
| 67 |
+
assert TR3(cos(y - x*(y - x))) == cos(x*(x - y) + y)
|
| 68 |
+
assert cos(pi/2 + x) == -sin(x)
|
| 69 |
+
assert cos(30*pi/2 + x) == -cos(x)
|
| 70 |
+
|
| 71 |
+
for f in (cos, sin, tan, cot, csc, sec):
|
| 72 |
+
i = f(pi*Rational(3, 7))
|
| 73 |
+
j = TR3(i)
|
| 74 |
+
assert verify_numerically(i, j) and i.func != j.func
|
| 75 |
+
|
| 76 |
+
with evaluate(False):
|
| 77 |
+
eq = cos(9*pi/22)
|
| 78 |
+
assert eq.has(9*pi) and TR3(eq) == sin(pi/11)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def test_TR4():
|
| 82 |
+
for i in [0, pi/6, pi/4, pi/3, pi/2]:
|
| 83 |
+
with evaluate(False):
|
| 84 |
+
eq = cos(i)
|
| 85 |
+
assert isinstance(eq, cos) and TR4(eq) == cos(i)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def test__TR56():
|
| 89 |
+
h = lambda x: 1 - x
|
| 90 |
+
assert T(sin(x)**3, sin, cos, h, 4, False) == sin(x)*(-cos(x)**2 + 1)
|
| 91 |
+
assert T(sin(x)**10, sin, cos, h, 4, False) == sin(x)**10
|
| 92 |
+
assert T(sin(x)**6, sin, cos, h, 6, False) == (-cos(x)**2 + 1)**3
|
| 93 |
+
assert T(sin(x)**6, sin, cos, h, 6, True) == sin(x)**6
|
| 94 |
+
assert T(sin(x)**8, sin, cos, h, 10, True) == (-cos(x)**2 + 1)**4
|
| 95 |
+
|
| 96 |
+
# issue 17137
|
| 97 |
+
assert T(sin(x)**I, sin, cos, h, 4, True) == sin(x)**I
|
| 98 |
+
assert T(sin(x)**(2*I + 1), sin, cos, h, 4, True) == sin(x)**(2*I + 1)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def test_TR5():
|
| 102 |
+
assert TR5(sin(x)**2) == -cos(x)**2 + 1
|
| 103 |
+
assert TR5(sin(x)**-2) == sin(x)**(-2)
|
| 104 |
+
assert TR5(sin(x)**4) == (-cos(x)**2 + 1)**2
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def test_TR6():
|
| 108 |
+
assert TR6(cos(x)**2) == -sin(x)**2 + 1
|
| 109 |
+
assert TR6(cos(x)**-2) == cos(x)**(-2)
|
| 110 |
+
assert TR6(cos(x)**4) == (-sin(x)**2 + 1)**2
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def test_TR7():
|
| 114 |
+
assert TR7(cos(x)**2) == cos(2*x)/2 + S.Half
|
| 115 |
+
assert TR7(cos(x)**2 + 1) == cos(2*x)/2 + Rational(3, 2)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def test_TR8():
|
| 119 |
+
assert TR8(cos(2)*cos(3)) == cos(5)/2 + cos(1)/2
|
| 120 |
+
assert TR8(cos(2)*sin(3)) == sin(5)/2 + sin(1)/2
|
| 121 |
+
assert TR8(sin(2)*sin(3)) == -cos(5)/2 + cos(1)/2
|
| 122 |
+
assert TR8(sin(1)*sin(2)*sin(3)) == sin(4)/4 - sin(6)/4 + sin(2)/4
|
| 123 |
+
assert TR8(cos(2)*cos(3)*cos(4)*cos(5)) == \
|
| 124 |
+
cos(4)/4 + cos(10)/8 + cos(2)/8 + cos(8)/8 + cos(14)/8 + \
|
| 125 |
+
cos(6)/8 + Rational(1, 8)
|
| 126 |
+
assert TR8(cos(2)*cos(3)*cos(4)*cos(5)*cos(6)) == \
|
| 127 |
+
cos(10)/8 + cos(4)/8 + 3*cos(2)/16 + cos(16)/16 + cos(8)/8 + \
|
| 128 |
+
cos(14)/16 + cos(20)/16 + cos(12)/16 + Rational(1, 16) + cos(6)/8
|
| 129 |
+
assert TR8(sin(pi*Rational(3, 7))**2*cos(pi*Rational(3, 7))**2/(16*sin(pi/7)**2)) == Rational(1, 64)
|
| 130 |
+
|
| 131 |
+
def test_TR9():
|
| 132 |
+
a = S.Half
|
| 133 |
+
b = 3*a
|
| 134 |
+
assert TR9(a) == a
|
| 135 |
+
assert TR9(cos(1) + cos(2)) == 2*cos(a)*cos(b)
|
| 136 |
+
assert TR9(cos(1) - cos(2)) == 2*sin(a)*sin(b)
|
| 137 |
+
assert TR9(sin(1) - sin(2)) == -2*sin(a)*cos(b)
|
| 138 |
+
assert TR9(sin(1) + sin(2)) == 2*sin(b)*cos(a)
|
| 139 |
+
assert TR9(cos(1) + 2*sin(1) + 2*sin(2)) == cos(1) + 4*sin(b)*cos(a)
|
| 140 |
+
assert TR9(cos(4) + cos(2) + 2*cos(1)*cos(3)) == 4*cos(1)*cos(3)
|
| 141 |
+
assert TR9((cos(4) + cos(2))/cos(3)/2 + cos(3)) == 2*cos(1)*cos(2)
|
| 142 |
+
assert TR9(cos(3) + cos(4) + cos(5) + cos(6)) == \
|
| 143 |
+
4*cos(S.Half)*cos(1)*cos(Rational(9, 2))
|
| 144 |
+
assert TR9(cos(3) + cos(3)*cos(2)) == cos(3) + cos(2)*cos(3)
|
| 145 |
+
assert TR9(-cos(y) + cos(x*y)) == -2*sin(x*y/2 - y/2)*sin(x*y/2 + y/2)
|
| 146 |
+
assert TR9(-sin(y) + sin(x*y)) == 2*sin(x*y/2 - y/2)*cos(x*y/2 + y/2)
|
| 147 |
+
c = cos(x)
|
| 148 |
+
s = sin(x)
|
| 149 |
+
for si in ((1, 1), (1, -1), (-1, 1), (-1, -1)):
|
| 150 |
+
for a in ((c, s), (s, c), (cos(x), cos(x*y)), (sin(x), sin(x*y))):
|
| 151 |
+
args = zip(si, a)
|
| 152 |
+
ex = Add(*[Mul(*ai) for ai in args])
|
| 153 |
+
t = TR9(ex)
|
| 154 |
+
assert not (a[0].func == a[1].func and (
|
| 155 |
+
not verify_numerically(ex, t.expand(trig=True)) or t.is_Add)
|
| 156 |
+
or a[1].func != a[0].func and ex != t)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def test_TR10():
|
| 160 |
+
assert TR10(cos(a + b)) == -sin(a)*sin(b) + cos(a)*cos(b)
|
| 161 |
+
assert TR10(sin(a + b)) == sin(a)*cos(b) + sin(b)*cos(a)
|
| 162 |
+
assert TR10(sin(a + b + c)) == \
|
| 163 |
+
(-sin(a)*sin(b) + cos(a)*cos(b))*sin(c) + \
|
| 164 |
+
(sin(a)*cos(b) + sin(b)*cos(a))*cos(c)
|
| 165 |
+
assert TR10(cos(a + b + c)) == \
|
| 166 |
+
(-sin(a)*sin(b) + cos(a)*cos(b))*cos(c) - \
|
| 167 |
+
(sin(a)*cos(b) + sin(b)*cos(a))*sin(c)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def test_TR10i():
|
| 171 |
+
assert TR10i(cos(1)*cos(3) + sin(1)*sin(3)) == cos(2)
|
| 172 |
+
assert TR10i(cos(1)*cos(3) - sin(1)*sin(3)) == cos(4)
|
| 173 |
+
assert TR10i(cos(1)*sin(3) - sin(1)*cos(3)) == sin(2)
|
| 174 |
+
assert TR10i(cos(1)*sin(3) + sin(1)*cos(3)) == sin(4)
|
| 175 |
+
assert TR10i(cos(1)*sin(3) + sin(1)*cos(3) + 7) == sin(4) + 7
|
| 176 |
+
assert TR10i(cos(1)*sin(3) + sin(1)*cos(3) + cos(3)) == cos(3) + sin(4)
|
| 177 |
+
assert TR10i(2*cos(1)*sin(3) + 2*sin(1)*cos(3) + cos(3)) == \
|
| 178 |
+
2*sin(4) + cos(3)
|
| 179 |
+
assert TR10i(cos(2)*cos(3) + sin(2)*(cos(1)*sin(2) + cos(2)*sin(1))) == \
|
| 180 |
+
cos(1)
|
| 181 |
+
eq = (cos(2)*cos(3) + sin(2)*(
|
| 182 |
+
cos(1)*sin(2) + cos(2)*sin(1)))*cos(5) + sin(1)*sin(5)
|
| 183 |
+
assert TR10i(eq) == TR10i(eq.expand()) == cos(4)
|
| 184 |
+
assert TR10i(sqrt(2)*cos(x)*x + sqrt(6)*sin(x)*x) == \
|
| 185 |
+
2*sqrt(2)*x*sin(x + pi/6)
|
| 186 |
+
assert TR10i(cos(x)/sqrt(6) + sin(x)/sqrt(2) +
|
| 187 |
+
cos(x)/sqrt(6)/3 + sin(x)/sqrt(2)/3) == 4*sqrt(6)*sin(x + pi/6)/9
|
| 188 |
+
assert TR10i(cos(x)/sqrt(6) + sin(x)/sqrt(2) +
|
| 189 |
+
cos(y)/sqrt(6)/3 + sin(y)/sqrt(2)/3) == \
|
| 190 |
+
sqrt(6)*sin(x + pi/6)/3 + sqrt(6)*sin(y + pi/6)/9
|
| 191 |
+
assert TR10i(cos(x) + sqrt(3)*sin(x) + 2*sqrt(3)*cos(x + pi/6)) == 4*cos(x)
|
| 192 |
+
assert TR10i(cos(x) + sqrt(3)*sin(x) +
|
| 193 |
+
2*sqrt(3)*cos(x + pi/6) + 4*sin(x)) == 4*sqrt(2)*sin(x + pi/4)
|
| 194 |
+
assert TR10i(cos(2)*sin(3) + sin(2)*cos(4)) == \
|
| 195 |
+
sin(2)*cos(4) + sin(3)*cos(2)
|
| 196 |
+
|
| 197 |
+
A = Symbol('A', commutative=False)
|
| 198 |
+
assert TR10i(sqrt(2)*cos(x)*A + sqrt(6)*sin(x)*A) == \
|
| 199 |
+
2*sqrt(2)*sin(x + pi/6)*A
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
c = cos(x)
|
| 203 |
+
s = sin(x)
|
| 204 |
+
h = sin(y)
|
| 205 |
+
r = cos(y)
|
| 206 |
+
for si in ((1, 1), (1, -1), (-1, 1), (-1, -1)):
|
| 207 |
+
for argsi in ((c*r, s*h), (c*h, s*r)): # explicit 2-args
|
| 208 |
+
args = zip(si, argsi)
|
| 209 |
+
ex = Add(*[Mul(*ai) for ai in args])
|
| 210 |
+
t = TR10i(ex)
|
| 211 |
+
assert not (ex - t.expand(trig=True) or t.is_Add)
|
| 212 |
+
|
| 213 |
+
c = cos(x)
|
| 214 |
+
s = sin(x)
|
| 215 |
+
h = sin(pi/6)
|
| 216 |
+
r = cos(pi/6)
|
| 217 |
+
for si in ((1, 1), (1, -1), (-1, 1), (-1, -1)):
|
| 218 |
+
for argsi in ((c*r, s*h), (c*h, s*r)): # induced
|
| 219 |
+
args = zip(si, argsi)
|
| 220 |
+
ex = Add(*[Mul(*ai) for ai in args])
|
| 221 |
+
t = TR10i(ex)
|
| 222 |
+
assert not (ex - t.expand(trig=True) or t.is_Add)
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def test_TR11():
|
| 226 |
+
|
| 227 |
+
assert TR11(sin(2*x)) == 2*sin(x)*cos(x)
|
| 228 |
+
assert TR11(sin(4*x)) == 4*((-sin(x)**2 + cos(x)**2)*sin(x)*cos(x))
|
| 229 |
+
assert TR11(sin(x*Rational(4, 3))) == \
|
| 230 |
+
4*((-sin(x/3)**2 + cos(x/3)**2)*sin(x/3)*cos(x/3))
|
| 231 |
+
|
| 232 |
+
assert TR11(cos(2*x)) == -sin(x)**2 + cos(x)**2
|
| 233 |
+
assert TR11(cos(4*x)) == \
|
| 234 |
+
(-sin(x)**2 + cos(x)**2)**2 - 4*sin(x)**2*cos(x)**2
|
| 235 |
+
|
| 236 |
+
assert TR11(cos(2)) == cos(2)
|
| 237 |
+
|
| 238 |
+
assert TR11(cos(pi*Rational(3, 7)), pi*Rational(2, 7)) == -cos(pi*Rational(2, 7))**2 + sin(pi*Rational(2, 7))**2
|
| 239 |
+
assert TR11(cos(4), 2) == -sin(2)**2 + cos(2)**2
|
| 240 |
+
assert TR11(cos(6), 2) == cos(6)
|
| 241 |
+
assert TR11(sin(x)/cos(x/2), x/2) == 2*sin(x/2)
|
| 242 |
+
|
| 243 |
+
def test__TR11():
|
| 244 |
+
|
| 245 |
+
assert _TR11(sin(x/3)*sin(2*x)*sin(x/4)/(cos(x/6)*cos(x/8))) == \
|
| 246 |
+
4*sin(x/8)*sin(x/6)*sin(2*x),_TR11(sin(x/3)*sin(2*x)*sin(x/4)/(cos(x/6)*cos(x/8)))
|
| 247 |
+
assert _TR11(sin(x/3)/cos(x/6)) == 2*sin(x/6)
|
| 248 |
+
|
| 249 |
+
assert _TR11(cos(x/6)/sin(x/3)) == 1/(2*sin(x/6))
|
| 250 |
+
assert _TR11(sin(2*x)*cos(x/8)/sin(x/4)) == sin(2*x)/(2*sin(x/8)), _TR11(sin(2*x)*cos(x/8)/sin(x/4))
|
| 251 |
+
assert _TR11(sin(x)/sin(x/2)) == 2*cos(x/2)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
def test_TR12():
|
| 255 |
+
assert TR12(tan(x + y)) == (tan(x) + tan(y))/(-tan(x)*tan(y) + 1)
|
| 256 |
+
assert TR12(tan(x + y + z)) ==\
|
| 257 |
+
(tan(z) + (tan(x) + tan(y))/(-tan(x)*tan(y) + 1))/(
|
| 258 |
+
1 - (tan(x) + tan(y))*tan(z)/(-tan(x)*tan(y) + 1))
|
| 259 |
+
assert TR12(tan(x*y)) == tan(x*y)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def test_TR13():
|
| 263 |
+
assert TR13(tan(3)*tan(2)) == -tan(2)/tan(5) - tan(3)/tan(5) + 1
|
| 264 |
+
assert TR13(cot(3)*cot(2)) == 1 + cot(3)*cot(5) + cot(2)*cot(5)
|
| 265 |
+
assert TR13(tan(1)*tan(2)*tan(3)) == \
|
| 266 |
+
(-tan(2)/tan(5) - tan(3)/tan(5) + 1)*tan(1)
|
| 267 |
+
assert TR13(tan(1)*tan(2)*cot(3)) == \
|
| 268 |
+
(-tan(2)/tan(3) + 1 - tan(1)/tan(3))*cot(3)
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def test_L():
|
| 272 |
+
assert L(cos(x) + sin(x)) == 2
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def test_fu():
|
| 276 |
+
|
| 277 |
+
assert fu(sin(50)**2 + cos(50)**2 + sin(pi/6)) == Rational(3, 2)
|
| 278 |
+
assert fu(sqrt(6)*cos(x) + sqrt(2)*sin(x)) == 2*sqrt(2)*sin(x + pi/3)
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
eq = sin(x)**4 - cos(y)**2 + sin(y)**2 + 2*cos(x)**2
|
| 282 |
+
assert fu(eq) == cos(x)**4 - 2*cos(y)**2 + 2
|
| 283 |
+
|
| 284 |
+
assert fu(S.Half - cos(2*x)/2) == sin(x)**2
|
| 285 |
+
|
| 286 |
+
assert fu(sin(a)*(cos(b) - sin(b)) + cos(a)*(sin(b) + cos(b))) == \
|
| 287 |
+
sqrt(2)*sin(a + b + pi/4)
|
| 288 |
+
|
| 289 |
+
assert fu(sqrt(3)*cos(x)/2 + sin(x)/2) == sin(x + pi/3)
|
| 290 |
+
|
| 291 |
+
assert fu(1 - sin(2*x)**2/4 - sin(y)**2 - cos(x)**4) == \
|
| 292 |
+
-cos(x)**2 + cos(y)**2
|
| 293 |
+
|
| 294 |
+
assert fu(cos(pi*Rational(4, 9))) == sin(pi/18)
|
| 295 |
+
assert fu(cos(pi/9)*cos(pi*Rational(2, 9))*cos(pi*Rational(3, 9))*cos(pi*Rational(4, 9))) == Rational(1, 16)
|
| 296 |
+
|
| 297 |
+
assert fu(
|
| 298 |
+
tan(pi*Rational(7, 18)) + tan(pi*Rational(5, 18)) - sqrt(3)*tan(pi*Rational(5, 18))*tan(pi*Rational(7, 18))) == \
|
| 299 |
+
-sqrt(3)
|
| 300 |
+
|
| 301 |
+
assert fu(tan(1)*tan(2)) == tan(1)*tan(2)
|
| 302 |
+
|
| 303 |
+
expr = Mul(*[cos(2**i) for i in range(10)])
|
| 304 |
+
assert fu(expr) == sin(1024)/(1024*sin(1))
|
| 305 |
+
|
| 306 |
+
# issue #18059:
|
| 307 |
+
assert fu(cos(x) + sqrt(sin(x)**2)) == cos(x) + sqrt(sin(x)**2)
|
| 308 |
+
|
| 309 |
+
assert fu((-14*sin(x)**3 + 35*sin(x) + 6*sqrt(3)*cos(x)**3 + 9*sqrt(3)*cos(x))/((cos(2*x) + 4))) == \
|
| 310 |
+
7*sin(x) + 3*sqrt(3)*cos(x)
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
def test_objective():
|
| 314 |
+
assert fu(sin(x)/cos(x), measure=lambda x: x.count_ops()) == \
|
| 315 |
+
tan(x)
|
| 316 |
+
assert fu(sin(x)/cos(x), measure=lambda x: -x.count_ops()) == \
|
| 317 |
+
sin(x)/cos(x)
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def test_process_common_addends():
|
| 321 |
+
# this tests that the args are not evaluated as they are given to do
|
| 322 |
+
# and that key2 works when key1 is False
|
| 323 |
+
do = lambda x: Add(*[i**(i%2) for i in x.args])
|
| 324 |
+
assert process_common_addends(Add(*[1, 2, 3, 4], evaluate=False), do,
|
| 325 |
+
key2=lambda x: x%2, key1=False) == 1**1 + 3**1 + 2**0 + 4**0
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def test_trig_split():
|
| 329 |
+
assert trig_split(cos(x), cos(y)) == (1, 1, 1, x, y, True)
|
| 330 |
+
assert trig_split(2*cos(x), -2*cos(y)) == (2, 1, -1, x, y, True)
|
| 331 |
+
assert trig_split(cos(x)*sin(y), cos(y)*sin(y)) == \
|
| 332 |
+
(sin(y), 1, 1, x, y, True)
|
| 333 |
+
|
| 334 |
+
assert trig_split(cos(x), -sqrt(3)*sin(x), two=True) == \
|
| 335 |
+
(2, 1, -1, x, pi/6, False)
|
| 336 |
+
assert trig_split(cos(x), sin(x), two=True) == \
|
| 337 |
+
(sqrt(2), 1, 1, x, pi/4, False)
|
| 338 |
+
assert trig_split(cos(x), -sin(x), two=True) == \
|
| 339 |
+
(sqrt(2), 1, -1, x, pi/4, False)
|
| 340 |
+
assert trig_split(sqrt(2)*cos(x), -sqrt(6)*sin(x), two=True) == \
|
| 341 |
+
(2*sqrt(2), 1, -1, x, pi/6, False)
|
| 342 |
+
assert trig_split(-sqrt(6)*cos(x), -sqrt(2)*sin(x), two=True) == \
|
| 343 |
+
(-2*sqrt(2), 1, 1, x, pi/3, False)
|
| 344 |
+
assert trig_split(cos(x)/sqrt(6), sin(x)/sqrt(2), two=True) == \
|
| 345 |
+
(sqrt(6)/3, 1, 1, x, pi/6, False)
|
| 346 |
+
assert trig_split(-sqrt(6)*cos(x)*sin(y),
|
| 347 |
+
-sqrt(2)*sin(x)*sin(y), two=True) == \
|
| 348 |
+
(-2*sqrt(2)*sin(y), 1, 1, x, pi/3, False)
|
| 349 |
+
|
| 350 |
+
assert trig_split(cos(x), sin(x)) is None
|
| 351 |
+
assert trig_split(cos(x), sin(z)) is None
|
| 352 |
+
assert trig_split(2*cos(x), -sin(x)) is None
|
| 353 |
+
assert trig_split(cos(x), -sqrt(3)*sin(x)) is None
|
| 354 |
+
assert trig_split(cos(x)*cos(y), sin(x)*sin(z)) is None
|
| 355 |
+
assert trig_split(cos(x)*cos(y), sin(x)*sin(y)) is None
|
| 356 |
+
assert trig_split(-sqrt(6)*cos(x), sqrt(2)*sin(x)*sin(y), two=True) is \
|
| 357 |
+
None
|
| 358 |
+
|
| 359 |
+
assert trig_split(sqrt(3)*sqrt(x), cos(3), two=True) is None
|
| 360 |
+
assert trig_split(sqrt(3)*root(x, 3), sin(3)*cos(2), two=True) is None
|
| 361 |
+
assert trig_split(cos(5)*cos(6), cos(7)*sin(5), two=True) is None
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def test_TRmorrie():
|
| 365 |
+
assert TRmorrie(7*Mul(*[cos(i) for i in range(10)])) == \
|
| 366 |
+
7*sin(12)*sin(16)*cos(5)*cos(7)*cos(9)/(64*sin(1)*sin(3))
|
| 367 |
+
assert TRmorrie(x) == x
|
| 368 |
+
assert TRmorrie(2*x) == 2*x
|
| 369 |
+
e = cos(pi/7)*cos(pi*Rational(2, 7))*cos(pi*Rational(4, 7))
|
| 370 |
+
assert TR8(TRmorrie(e)) == Rational(-1, 8)
|
| 371 |
+
e = Mul(*[cos(2**i*pi/17) for i in range(1, 17)])
|
| 372 |
+
assert TR8(TR3(TRmorrie(e))) == Rational(1, 65536)
|
| 373 |
+
# issue 17063
|
| 374 |
+
eq = cos(x)/cos(x/2)
|
| 375 |
+
assert TRmorrie(eq) == eq
|
| 376 |
+
# issue #20430
|
| 377 |
+
eq = cos(x/2)*sin(x/2)*cos(x)**3
|
| 378 |
+
assert TRmorrie(eq) == sin(2*x)*cos(x)**2/4
|
| 379 |
+
|
| 380 |
+
|
| 381 |
+
def test_TRpower():
|
| 382 |
+
assert TRpower(1/sin(x)**2) == 1/sin(x)**2
|
| 383 |
+
assert TRpower(cos(x)**3*sin(x/2)**4) == \
|
| 384 |
+
(3*cos(x)/4 + cos(3*x)/4)*(-cos(x)/2 + cos(2*x)/8 + Rational(3, 8))
|
| 385 |
+
for k in range(2, 8):
|
| 386 |
+
assert verify_numerically(sin(x)**k, TRpower(sin(x)**k))
|
| 387 |
+
assert verify_numerically(cos(x)**k, TRpower(cos(x)**k))
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
def test_hyper_as_trig():
|
| 391 |
+
from sympy.simplify.fu import _osborne, _osbornei
|
| 392 |
+
|
| 393 |
+
eq = sinh(x)**2 + cosh(x)**2
|
| 394 |
+
t, f = hyper_as_trig(eq)
|
| 395 |
+
assert f(fu(t)) == cosh(2*x)
|
| 396 |
+
e, f = hyper_as_trig(tanh(x + y))
|
| 397 |
+
assert f(TR12(e)) == (tanh(x) + tanh(y))/(tanh(x)*tanh(y) + 1)
|
| 398 |
+
|
| 399 |
+
d = Dummy()
|
| 400 |
+
assert _osborne(sinh(x), d) == I*sin(x*d)
|
| 401 |
+
assert _osborne(tanh(x), d) == I*tan(x*d)
|
| 402 |
+
assert _osborne(coth(x), d) == cot(x*d)/I
|
| 403 |
+
assert _osborne(cosh(x), d) == cos(x*d)
|
| 404 |
+
assert _osborne(sech(x), d) == sec(x*d)
|
| 405 |
+
assert _osborne(csch(x), d) == csc(x*d)/I
|
| 406 |
+
for func in (sinh, cosh, tanh, coth, sech, csch):
|
| 407 |
+
h = func(pi)
|
| 408 |
+
assert _osbornei(_osborne(h, d), d) == h
|
| 409 |
+
# /!\ the _osborne functions are not meant to work
|
| 410 |
+
# in the o(i(trig, d), d) direction so we just check
|
| 411 |
+
# that they work as they are supposed to work
|
| 412 |
+
assert _osbornei(cos(x*y + z), y) == cosh(x + z*I)
|
| 413 |
+
assert _osbornei(sin(x*y + z), y) == sinh(x + z*I)/I
|
| 414 |
+
assert _osbornei(tan(x*y + z), y) == tanh(x + z*I)/I
|
| 415 |
+
assert _osbornei(cot(x*y + z), y) == coth(x + z*I)*I
|
| 416 |
+
assert _osbornei(sec(x*y + z), y) == sech(x + z*I)
|
| 417 |
+
assert _osbornei(csc(x*y + z), y) == csch(x + z*I)*I
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
def test_TR12i():
|
| 421 |
+
ta, tb, tc = [tan(i) for i in (a, b, c)]
|
| 422 |
+
assert TR12i((ta + tb)/(-ta*tb + 1)) == tan(a + b)
|
| 423 |
+
assert TR12i((ta + tb)/(ta*tb - 1)) == -tan(a + b)
|
| 424 |
+
assert TR12i((-ta - tb)/(ta*tb - 1)) == tan(a + b)
|
| 425 |
+
eq = (ta + tb)/(-ta*tb + 1)**2*(-3*ta - 3*tc)/(2*(ta*tc - 1))
|
| 426 |
+
assert TR12i(eq.expand()) == \
|
| 427 |
+
-3*tan(a + b)*tan(a + c)/(tan(a) + tan(b) - 1)/2
|
| 428 |
+
assert TR12i(tan(x)/sin(x)) == tan(x)/sin(x)
|
| 429 |
+
eq = (ta + cos(2))/(-ta*tb + 1)
|
| 430 |
+
assert TR12i(eq) == eq
|
| 431 |
+
eq = (ta + tb + 2)**2/(-ta*tb + 1)
|
| 432 |
+
assert TR12i(eq) == eq
|
| 433 |
+
eq = ta/(-ta*tb + 1)
|
| 434 |
+
assert TR12i(eq) == eq
|
| 435 |
+
eq = (((ta + tb)*(a + 1)).expand())**2/(ta*tb - 1)
|
| 436 |
+
assert TR12i(eq) == -(a + 1)**2*tan(a + b)
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
def test_TR14():
|
| 440 |
+
eq = (cos(x) - 1)*(cos(x) + 1)
|
| 441 |
+
ans = -sin(x)**2
|
| 442 |
+
assert TR14(eq) == ans
|
| 443 |
+
assert TR14(1/eq) == 1/ans
|
| 444 |
+
assert TR14((cos(x) - 1)**2*(cos(x) + 1)**2) == ans**2
|
| 445 |
+
assert TR14((cos(x) - 1)**2*(cos(x) + 1)**3) == ans**2*(cos(x) + 1)
|
| 446 |
+
assert TR14((cos(x) - 1)**3*(cos(x) + 1)**2) == ans**2*(cos(x) - 1)
|
| 447 |
+
eq = (cos(x) - 1)**y*(cos(x) + 1)**y
|
| 448 |
+
assert TR14(eq) == eq
|
| 449 |
+
eq = (cos(x) - 2)**y*(cos(x) + 1)
|
| 450 |
+
assert TR14(eq) == eq
|
| 451 |
+
eq = (tan(x) - 2)**2*(cos(x) + 1)
|
| 452 |
+
assert TR14(eq) == eq
|
| 453 |
+
i = symbols('i', integer=True)
|
| 454 |
+
assert TR14((cos(x) - 1)**i*(cos(x) + 1)**i) == ans**i
|
| 455 |
+
assert TR14((sin(x) - 1)**i*(sin(x) + 1)**i) == (-cos(x)**2)**i
|
| 456 |
+
# could use extraction in this case
|
| 457 |
+
eq = (cos(x) - 1)**(i + 1)*(cos(x) + 1)**i
|
| 458 |
+
assert TR14(eq) in [(cos(x) - 1)*ans**i, eq]
|
| 459 |
+
|
| 460 |
+
assert TR14((sin(x) - 1)*(sin(x) + 1)) == -cos(x)**2
|
| 461 |
+
p1 = (cos(x) + 1)*(cos(x) - 1)
|
| 462 |
+
p2 = (cos(y) - 1)*2*(cos(y) + 1)
|
| 463 |
+
p3 = (3*(cos(y) - 1))*(3*(cos(y) + 1))
|
| 464 |
+
assert TR14(p1*p2*p3*(x - 1)) == -18*((x - 1)*sin(x)**2*sin(y)**4)
|
| 465 |
+
|
| 466 |
+
|
| 467 |
+
def test_TR15_16_17():
|
| 468 |
+
assert TR15(1 - 1/sin(x)**2) == -cot(x)**2
|
| 469 |
+
assert TR16(1 - 1/cos(x)**2) == -tan(x)**2
|
| 470 |
+
assert TR111(1 - 1/tan(x)**2) == 1 - cot(x)**2
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
def test_as_f_sign_1():
|
| 474 |
+
assert as_f_sign_1(x + 1) == (1, x, 1)
|
| 475 |
+
assert as_f_sign_1(x - 1) == (1, x, -1)
|
| 476 |
+
assert as_f_sign_1(-x + 1) == (-1, x, -1)
|
| 477 |
+
assert as_f_sign_1(-x - 1) == (-1, x, 1)
|
| 478 |
+
assert as_f_sign_1(2*x + 2) == (2, x, 1)
|
| 479 |
+
assert as_f_sign_1(x*y - y) == (y, x, -1)
|
| 480 |
+
assert as_f_sign_1(-x*y + y) == (-y, x, -1)
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
def test_issue_25590():
|
| 484 |
+
A = Symbol('A', commutative=False)
|
| 485 |
+
B = Symbol('B', commutative=False)
|
| 486 |
+
|
| 487 |
+
assert TR8(2*cos(x)*sin(x)*B*A) == sin(2*x)*B*A
|
| 488 |
+
assert TR13(tan(2)*tan(3)*B*A) == (-tan(2)/tan(5) - tan(3)/tan(5) + 1)*B*A
|
| 489 |
+
|
| 490 |
+
# XXX The result may not be optimal than
|
| 491 |
+
# sin(2*x)*B*A + cos(x)**2 and may change in the future
|
| 492 |
+
assert (2*cos(x)*sin(x)*B*A + cos(x)**2).simplify() == sin(2*x)*B*A + cos(2*x)/2 + S.One/2
|
lib/python3.12/site-packages/sympy/simplify/tests/test_function.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
""" Unit tests for Hyper_Function"""
|
| 2 |
+
from sympy.core import symbols, Dummy, Tuple, S, Rational
|
| 3 |
+
from sympy.functions import hyper
|
| 4 |
+
|
| 5 |
+
from sympy.simplify.hyperexpand import Hyper_Function
|
| 6 |
+
|
| 7 |
+
def test_attrs():
|
| 8 |
+
a, b = symbols('a, b', cls=Dummy)
|
| 9 |
+
f = Hyper_Function([2, a], [b])
|
| 10 |
+
assert f.ap == Tuple(2, a)
|
| 11 |
+
assert f.bq == Tuple(b)
|
| 12 |
+
assert f.args == (Tuple(2, a), Tuple(b))
|
| 13 |
+
assert f.sizes == (2, 1)
|
| 14 |
+
|
| 15 |
+
def test_call():
|
| 16 |
+
a, b, x = symbols('a, b, x', cls=Dummy)
|
| 17 |
+
f = Hyper_Function([2, a], [b])
|
| 18 |
+
assert f(x) == hyper([2, a], [b], x)
|
| 19 |
+
|
| 20 |
+
def test_has():
|
| 21 |
+
a, b, c = symbols('a, b, c', cls=Dummy)
|
| 22 |
+
f = Hyper_Function([2, -a], [b])
|
| 23 |
+
assert f.has(a)
|
| 24 |
+
assert f.has(Tuple(b))
|
| 25 |
+
assert not f.has(c)
|
| 26 |
+
|
| 27 |
+
def test_eq():
|
| 28 |
+
assert Hyper_Function([1], []) == Hyper_Function([1], [])
|
| 29 |
+
assert (Hyper_Function([1], []) != Hyper_Function([1], [])) is False
|
| 30 |
+
assert Hyper_Function([1], []) != Hyper_Function([2], [])
|
| 31 |
+
assert Hyper_Function([1], []) != Hyper_Function([1, 2], [])
|
| 32 |
+
assert Hyper_Function([1], []) != Hyper_Function([1], [2])
|
| 33 |
+
|
| 34 |
+
def test_gamma():
|
| 35 |
+
assert Hyper_Function([2, 3], [-1]).gamma == 0
|
| 36 |
+
assert Hyper_Function([-2, -3], [-1]).gamma == 2
|
| 37 |
+
n = Dummy(integer=True)
|
| 38 |
+
assert Hyper_Function([-1, n, 1], []).gamma == 1
|
| 39 |
+
assert Hyper_Function([-1, -n, 1], []).gamma == 1
|
| 40 |
+
p = Dummy(integer=True, positive=True)
|
| 41 |
+
assert Hyper_Function([-1, p, 1], []).gamma == 1
|
| 42 |
+
assert Hyper_Function([-1, -p, 1], []).gamma == 2
|
| 43 |
+
|
| 44 |
+
def test_suitable_origin():
|
| 45 |
+
assert Hyper_Function((S.Half,), (Rational(3, 2),))._is_suitable_origin() is True
|
| 46 |
+
assert Hyper_Function((S.Half,), (S.Half,))._is_suitable_origin() is False
|
| 47 |
+
assert Hyper_Function((S.Half,), (Rational(-1, 2),))._is_suitable_origin() is False
|
| 48 |
+
assert Hyper_Function((S.Half,), (0,))._is_suitable_origin() is False
|
| 49 |
+
assert Hyper_Function((S.Half,), (-1, 1,))._is_suitable_origin() is False
|
| 50 |
+
assert Hyper_Function((S.Half, 0), (1,))._is_suitable_origin() is False
|
| 51 |
+
assert Hyper_Function((S.Half, 1),
|
| 52 |
+
(2, Rational(-2, 3)))._is_suitable_origin() is True
|
| 53 |
+
assert Hyper_Function((S.Half, 1),
|
| 54 |
+
(2, Rational(-2, 3), Rational(3, 2)))._is_suitable_origin() is True
|
lib/python3.12/site-packages/sympy/simplify/tests/test_gammasimp.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.function import Function
|
| 2 |
+
from sympy.core.numbers import (Rational, pi)
|
| 3 |
+
from sympy.core.singleton import S
|
| 4 |
+
from sympy.core.symbol import symbols
|
| 5 |
+
from sympy.functions.combinatorial.factorials import (rf, binomial, factorial)
|
| 6 |
+
from sympy.functions.elementary.exponential import exp
|
| 7 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 8 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 9 |
+
from sympy.functions.elementary.trigonometric import (cos, sin)
|
| 10 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 11 |
+
from sympy.simplify.gammasimp import gammasimp
|
| 12 |
+
from sympy.simplify.powsimp import powsimp
|
| 13 |
+
from sympy.simplify.simplify import simplify
|
| 14 |
+
|
| 15 |
+
from sympy.abc import x, y, n, k
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_gammasimp():
|
| 19 |
+
R = Rational
|
| 20 |
+
|
| 21 |
+
# was part of test_combsimp_gamma() in test_combsimp.py
|
| 22 |
+
assert gammasimp(gamma(x)) == gamma(x)
|
| 23 |
+
assert gammasimp(gamma(x + 1)/x) == gamma(x)
|
| 24 |
+
assert gammasimp(gamma(x)/(x - 1)) == gamma(x - 1)
|
| 25 |
+
assert gammasimp(x*gamma(x)) == gamma(x + 1)
|
| 26 |
+
assert gammasimp((x + 1)*gamma(x + 1)) == gamma(x + 2)
|
| 27 |
+
assert gammasimp(gamma(x + y)*(x + y)) == gamma(x + y + 1)
|
| 28 |
+
assert gammasimp(x/gamma(x + 1)) == 1/gamma(x)
|
| 29 |
+
assert gammasimp((x + 1)**2/gamma(x + 2)) == (x + 1)/gamma(x + 1)
|
| 30 |
+
assert gammasimp(x*gamma(x) + gamma(x + 3)/(x + 2)) == \
|
| 31 |
+
(x + 2)*gamma(x + 1)
|
| 32 |
+
|
| 33 |
+
assert gammasimp(gamma(2*x)*x) == gamma(2*x + 1)/2
|
| 34 |
+
assert gammasimp(gamma(2*x)/(x - S.Half)) == 2*gamma(2*x - 1)
|
| 35 |
+
|
| 36 |
+
assert gammasimp(gamma(x)*gamma(1 - x)) == pi/sin(pi*x)
|
| 37 |
+
assert gammasimp(gamma(x)*gamma(-x)) == -pi/(x*sin(pi*x))
|
| 38 |
+
assert gammasimp(1/gamma(x + 3)/gamma(1 - x)) == \
|
| 39 |
+
sin(pi*x)/(pi*x*(x + 1)*(x + 2))
|
| 40 |
+
|
| 41 |
+
assert gammasimp(factorial(n + 2)) == gamma(n + 3)
|
| 42 |
+
assert gammasimp(binomial(n, k)) == \
|
| 43 |
+
gamma(n + 1)/(gamma(k + 1)*gamma(-k + n + 1))
|
| 44 |
+
|
| 45 |
+
assert powsimp(gammasimp(
|
| 46 |
+
gamma(x)*gamma(x + S.Half)*gamma(y)/gamma(x + y))) == \
|
| 47 |
+
2**(-2*x + 1)*sqrt(pi)*gamma(2*x)*gamma(y)/gamma(x + y)
|
| 48 |
+
assert gammasimp(1/gamma(x)/gamma(x - Rational(1, 3))/gamma(x + Rational(1, 3))) == \
|
| 49 |
+
3**(3*x - Rational(3, 2))/(2*pi*gamma(3*x - 1))
|
| 50 |
+
assert simplify(
|
| 51 |
+
gamma(S.Half + x/2)*gamma(1 + x/2)/gamma(1 + x)/sqrt(pi)*2**x) == 1
|
| 52 |
+
assert gammasimp(gamma(Rational(-1, 4))*gamma(Rational(-3, 4))) == 16*sqrt(2)*pi/3
|
| 53 |
+
|
| 54 |
+
assert powsimp(gammasimp(gamma(2*x)/gamma(x))) == \
|
| 55 |
+
2**(2*x - 1)*gamma(x + S.Half)/sqrt(pi)
|
| 56 |
+
|
| 57 |
+
# issue 6792
|
| 58 |
+
e = (-gamma(k)*gamma(k + 2) + gamma(k + 1)**2)/gamma(k)**2
|
| 59 |
+
assert gammasimp(e) == -k
|
| 60 |
+
assert gammasimp(1/e) == -1/k
|
| 61 |
+
e = (gamma(x) + gamma(x + 1))/gamma(x)
|
| 62 |
+
assert gammasimp(e) == x + 1
|
| 63 |
+
assert gammasimp(1/e) == 1/(x + 1)
|
| 64 |
+
e = (gamma(x) + gamma(x + 2))*(gamma(x - 1) + gamma(x))/gamma(x)
|
| 65 |
+
assert gammasimp(e) == (x**2 + x + 1)*gamma(x + 1)/(x - 1)
|
| 66 |
+
e = (-gamma(k)*gamma(k + 2) + gamma(k + 1)**2)/gamma(k)**2
|
| 67 |
+
assert gammasimp(e**2) == k**2
|
| 68 |
+
assert gammasimp(e**2/gamma(k + 1)) == k/gamma(k)
|
| 69 |
+
a = R(1, 2) + R(1, 3)
|
| 70 |
+
b = a + R(1, 3)
|
| 71 |
+
assert gammasimp(gamma(2*k)/gamma(k)*gamma(k + a)*gamma(k + b)
|
| 72 |
+
) == 3*2**(2*k + 1)*3**(-3*k - 2)*sqrt(pi)*gamma(3*k + R(3, 2))/2
|
| 73 |
+
|
| 74 |
+
# issue 9699
|
| 75 |
+
assert gammasimp((x + 1)*factorial(x)/gamma(y)) == gamma(x + 2)/gamma(y)
|
| 76 |
+
assert gammasimp(rf(x + n, k)*binomial(n, k)).simplify() == Piecewise(
|
| 77 |
+
(gamma(n + 1)*gamma(k + n + x)/(gamma(k + 1)*gamma(n + x)*gamma(-k + n + 1)), n > -x),
|
| 78 |
+
((-1)**k*gamma(n + 1)*gamma(-n - x + 1)/(gamma(k + 1)*gamma(-k + n + 1)*gamma(-k - n - x + 1)), True))
|
| 79 |
+
|
| 80 |
+
A, B = symbols('A B', commutative=False)
|
| 81 |
+
assert gammasimp(e*B*A) == gammasimp(e)*B*A
|
| 82 |
+
|
| 83 |
+
# check iteration
|
| 84 |
+
assert gammasimp(gamma(2*k)/gamma(k)*gamma(-k - R(1, 2))) == (
|
| 85 |
+
-2**(2*k + 1)*sqrt(pi)/(2*((2*k + 1)*cos(pi*k))))
|
| 86 |
+
assert gammasimp(
|
| 87 |
+
gamma(k)*gamma(k + R(1, 3))*gamma(k + R(2, 3))/gamma(k*R(3, 2))) == (
|
| 88 |
+
3*2**(3*k + 1)*3**(-3*k - S.Half)*sqrt(pi)*gamma(k*R(3, 2) + S.Half)/2)
|
| 89 |
+
|
| 90 |
+
# issue 6153
|
| 91 |
+
assert gammasimp(gamma(Rational(1, 4))/gamma(Rational(5, 4))) == 4
|
| 92 |
+
|
| 93 |
+
# was part of test_combsimp() in test_combsimp.py
|
| 94 |
+
assert gammasimp(binomial(n + 2, k + S.Half)) == gamma(n + 3)/ \
|
| 95 |
+
(gamma(k + R(3, 2))*gamma(-k + n + R(5, 2)))
|
| 96 |
+
assert gammasimp(binomial(n + 2, k + 2.0)) == \
|
| 97 |
+
gamma(n + 3)/(gamma(k + 3.0)*gamma(-k + n + 1))
|
| 98 |
+
|
| 99 |
+
# issue 11548
|
| 100 |
+
assert gammasimp(binomial(0, x)) == sin(pi*x)/(pi*x)
|
| 101 |
+
|
| 102 |
+
e = gamma(n + Rational(1, 3))*gamma(n + R(2, 3))
|
| 103 |
+
assert gammasimp(e) == e
|
| 104 |
+
assert gammasimp(gamma(4*n + S.Half)/gamma(2*n - R(3, 4))) == \
|
| 105 |
+
2**(4*n - R(5, 2))*(8*n - 3)*gamma(2*n + R(3, 4))/sqrt(pi)
|
| 106 |
+
|
| 107 |
+
i, m = symbols('i m', integer = True)
|
| 108 |
+
e = gamma(exp(i))
|
| 109 |
+
assert gammasimp(e) == e
|
| 110 |
+
e = gamma(m + 3)
|
| 111 |
+
assert gammasimp(e) == e
|
| 112 |
+
e = gamma(m + 1)/(gamma(i + 1)*gamma(-i + m + 1))
|
| 113 |
+
assert gammasimp(e) == e
|
| 114 |
+
|
| 115 |
+
p = symbols("p", integer=True, positive=True)
|
| 116 |
+
assert gammasimp(gamma(-p + 4)) == gamma(-p + 4)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def test_issue_22606():
|
| 120 |
+
fx = Function('f')(x)
|
| 121 |
+
eq = x + gamma(y)
|
| 122 |
+
# seems like ans should be `eq`, not `(x*y + gamma(y + 1))/y`
|
| 123 |
+
ans = gammasimp(eq)
|
| 124 |
+
assert gammasimp(eq.subs(x, fx)).subs(fx, x) == ans
|
| 125 |
+
assert gammasimp(eq.subs(x, cos(x))).subs(cos(x), x) == ans
|
| 126 |
+
assert 1/gammasimp(1/eq) == ans
|
| 127 |
+
assert gammasimp(fx.subs(x, eq)).args[0] == ans
|
lib/python3.12/site-packages/sympy/simplify/tests/test_hyperexpand.py
ADDED
|
@@ -0,0 +1,1063 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.random import randrange
|
| 2 |
+
|
| 3 |
+
from sympy.simplify.hyperexpand import (ShiftA, ShiftB, UnShiftA, UnShiftB,
|
| 4 |
+
MeijerShiftA, MeijerShiftB, MeijerShiftC, MeijerShiftD,
|
| 5 |
+
MeijerUnShiftA, MeijerUnShiftB, MeijerUnShiftC,
|
| 6 |
+
MeijerUnShiftD,
|
| 7 |
+
ReduceOrder, reduce_order, apply_operators,
|
| 8 |
+
devise_plan, make_derivative_operator, Formula,
|
| 9 |
+
hyperexpand, Hyper_Function, G_Function,
|
| 10 |
+
reduce_order_meijer,
|
| 11 |
+
build_hypergeometric_formula)
|
| 12 |
+
from sympy.concrete.summations import Sum
|
| 13 |
+
from sympy.core.containers import Tuple
|
| 14 |
+
from sympy.core.expr import Expr
|
| 15 |
+
from sympy.core.numbers import I
|
| 16 |
+
from sympy.core.singleton import S
|
| 17 |
+
from sympy.core.symbol import symbols
|
| 18 |
+
from sympy.functions.combinatorial.factorials import binomial
|
| 19 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 20 |
+
from sympy.functions.special.hyper import (hyper, meijerg)
|
| 21 |
+
from sympy.abc import z, a, b, c
|
| 22 |
+
from sympy.testing.pytest import XFAIL, raises, slow, tooslow
|
| 23 |
+
from sympy.core.random import verify_numerically as tn
|
| 24 |
+
|
| 25 |
+
from sympy.core.numbers import (Rational, pi)
|
| 26 |
+
from sympy.functions.elementary.exponential import (exp, exp_polar, log)
|
| 27 |
+
from sympy.functions.elementary.hyperbolic import atanh
|
| 28 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 29 |
+
from sympy.functions.elementary.trigonometric import (asin, cos, sin)
|
| 30 |
+
from sympy.functions.special.bessel import besseli
|
| 31 |
+
from sympy.functions.special.error_functions import erf
|
| 32 |
+
from sympy.functions.special.gamma_functions import (gamma, lowergamma)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def test_branch_bug():
|
| 36 |
+
assert hyperexpand(hyper((Rational(-1, 3), S.Half), (Rational(2, 3), Rational(3, 2)), -z)) == \
|
| 37 |
+
-z**S('1/3')*lowergamma(exp_polar(I*pi)/3, z)/5 \
|
| 38 |
+
+ sqrt(pi)*erf(sqrt(z))/(5*sqrt(z))
|
| 39 |
+
assert hyperexpand(meijerg([Rational(7, 6), 1], [], [Rational(2, 3)], [Rational(1, 6), 0], z)) == \
|
| 40 |
+
2*z**S('2/3')*(2*sqrt(pi)*erf(sqrt(z))/sqrt(z) - 2*lowergamma(
|
| 41 |
+
Rational(2, 3), z)/z**S('2/3'))*gamma(Rational(2, 3))/gamma(Rational(5, 3))
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def test_hyperexpand():
|
| 45 |
+
# Luke, Y. L. (1969), The Special Functions and Their Approximations,
|
| 46 |
+
# Volume 1, section 6.2
|
| 47 |
+
|
| 48 |
+
assert hyperexpand(hyper([], [], z)) == exp(z)
|
| 49 |
+
assert hyperexpand(hyper([1, 1], [2], -z)*z) == log(1 + z)
|
| 50 |
+
assert hyperexpand(hyper([], [S.Half], -z**2/4)) == cos(z)
|
| 51 |
+
assert hyperexpand(z*hyper([], [S('3/2')], -z**2/4)) == sin(z)
|
| 52 |
+
assert hyperexpand(hyper([S('1/2'), S('1/2')], [S('3/2')], z**2)*z) \
|
| 53 |
+
== asin(z)
|
| 54 |
+
assert isinstance(Sum(binomial(2, z)*z**2, (z, 0, a)).doit(), Expr)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def can_do(ap, bq, numerical=True, div=1, lowerplane=False):
|
| 58 |
+
r = hyperexpand(hyper(ap, bq, z))
|
| 59 |
+
if r.has(hyper):
|
| 60 |
+
return False
|
| 61 |
+
if not numerical:
|
| 62 |
+
return True
|
| 63 |
+
repl = {}
|
| 64 |
+
randsyms = r.free_symbols - {z}
|
| 65 |
+
while randsyms:
|
| 66 |
+
# Only randomly generated parameters are checked.
|
| 67 |
+
for n, ai in enumerate(randsyms):
|
| 68 |
+
repl[ai] = randcplx(n)/div
|
| 69 |
+
if not any(b.is_Integer and b <= 0 for b in Tuple(*bq).subs(repl)):
|
| 70 |
+
break
|
| 71 |
+
[a, b, c, d] = [2, -1, 3, 1]
|
| 72 |
+
if lowerplane:
|
| 73 |
+
[a, b, c, d] = [2, -2, 3, -1]
|
| 74 |
+
return tn(
|
| 75 |
+
hyper(ap, bq, z).subs(repl),
|
| 76 |
+
r.replace(exp_polar, exp).subs(repl),
|
| 77 |
+
z, a=a, b=b, c=c, d=d)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def test_roach():
|
| 81 |
+
# Kelly B. Roach. Meijer G Function Representations.
|
| 82 |
+
# Section "Gallery"
|
| 83 |
+
assert can_do([S.Half], [Rational(9, 2)])
|
| 84 |
+
assert can_do([], [1, Rational(5, 2), 4])
|
| 85 |
+
assert can_do([Rational(-1, 2), 1, 2], [3, 4])
|
| 86 |
+
assert can_do([Rational(1, 3)], [Rational(-2, 3), Rational(-1, 2), S.Half, 1])
|
| 87 |
+
assert can_do([Rational(-3, 2), Rational(-1, 2)], [Rational(-5, 2), 1])
|
| 88 |
+
assert can_do([Rational(-3, 2), ], [Rational(-1, 2), S.Half]) # shine-integral
|
| 89 |
+
assert can_do([Rational(-3, 2), Rational(-1, 2)], [2]) # elliptic integrals
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
@XFAIL
|
| 93 |
+
def test_roach_fail():
|
| 94 |
+
assert can_do([Rational(-1, 2), 1], [Rational(1, 4), S.Half, Rational(3, 4)]) # PFDD
|
| 95 |
+
assert can_do([Rational(3, 2)], [Rational(5, 2), 5]) # struve function
|
| 96 |
+
assert can_do([Rational(-1, 2), S.Half, 1], [Rational(3, 2), Rational(5, 2)]) # polylog, pfdd
|
| 97 |
+
assert can_do([1, 2, 3], [S.Half, 4]) # XXX ?
|
| 98 |
+
assert can_do([S.Half], [Rational(-1, 3), Rational(-1, 2), Rational(-2, 3)]) # PFDD ?
|
| 99 |
+
|
| 100 |
+
# For the long table tests, see end of file
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def test_polynomial():
|
| 104 |
+
from sympy.core.numbers import oo
|
| 105 |
+
assert hyperexpand(hyper([], [-1], z)) is oo
|
| 106 |
+
assert hyperexpand(hyper([-2], [-1], z)) is oo
|
| 107 |
+
assert hyperexpand(hyper([0, 0], [-1], z)) == 1
|
| 108 |
+
assert can_do([-5, -2, randcplx(), randcplx()], [-10, randcplx()])
|
| 109 |
+
assert hyperexpand(hyper((-1, 1), (-2,), z)) == 1 + z/2
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def test_hyperexpand_bases():
|
| 113 |
+
assert hyperexpand(hyper([2], [a], z)) == \
|
| 114 |
+
a + z**(-a + 1)*(-a**2 + 3*a + z*(a - 1) - 2)*exp(z)* \
|
| 115 |
+
lowergamma(a - 1, z) - 1
|
| 116 |
+
# TODO [a+1, aRational(-1, 2)], [2*a]
|
| 117 |
+
assert hyperexpand(hyper([1, 2], [3], z)) == -2/z - 2*log(-z + 1)/z**2
|
| 118 |
+
assert hyperexpand(hyper([S.Half, 2], [Rational(3, 2)], z)) == \
|
| 119 |
+
-1/(2*z - 2) + atanh(sqrt(z))/sqrt(z)/2
|
| 120 |
+
assert hyperexpand(hyper([S.Half, S.Half], [Rational(5, 2)], z)) == \
|
| 121 |
+
(-3*z + 3)/4/(z*sqrt(-z + 1)) \
|
| 122 |
+
+ (6*z - 3)*asin(sqrt(z))/(4*z**Rational(3, 2))
|
| 123 |
+
assert hyperexpand(hyper([1, 2], [Rational(3, 2)], z)) == -1/(2*z - 2) \
|
| 124 |
+
- asin(sqrt(z))/(sqrt(z)*(2*z - 2)*sqrt(-z + 1))
|
| 125 |
+
assert hyperexpand(hyper([Rational(-1, 2) - 1, 1, 2], [S.Half, 3], z)) == \
|
| 126 |
+
sqrt(z)*(z*Rational(6, 7) - Rational(6, 5))*atanh(sqrt(z)) \
|
| 127 |
+
+ (-30*z**2 + 32*z - 6)/35/z - 6*log(-z + 1)/(35*z**2)
|
| 128 |
+
assert hyperexpand(hyper([1 + S.Half, 1, 1], [2, 2], z)) == \
|
| 129 |
+
-4*log(sqrt(-z + 1)/2 + S.Half)/z
|
| 130 |
+
# TODO hyperexpand(hyper([a], [2*a + 1], z))
|
| 131 |
+
# TODO [S.Half, a], [Rational(3, 2), a+1]
|
| 132 |
+
assert hyperexpand(hyper([2], [b, 1], z)) == \
|
| 133 |
+
z**(-b/2 + S.Half)*besseli(b - 1, 2*sqrt(z))*gamma(b) \
|
| 134 |
+
+ z**(-b/2 + 1)*besseli(b, 2*sqrt(z))*gamma(b)
|
| 135 |
+
# TODO [a], [a - S.Half, 2*a]
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def test_hyperexpand_parametric():
|
| 139 |
+
assert hyperexpand(hyper([a, S.Half + a], [S.Half], z)) \
|
| 140 |
+
== (1 + sqrt(z))**(-2*a)/2 + (1 - sqrt(z))**(-2*a)/2
|
| 141 |
+
assert hyperexpand(hyper([a, Rational(-1, 2) + a], [2*a], z)) \
|
| 142 |
+
== 2**(2*a - 1)*((-z + 1)**S.Half + 1)**(-2*a + 1)
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def test_shifted_sum():
|
| 146 |
+
from sympy.simplify.simplify import simplify
|
| 147 |
+
assert simplify(hyperexpand(z**4*hyper([2], [3, S('3/2')], -z**2))) \
|
| 148 |
+
== z*sin(2*z) + (-z**2 + S.Half)*cos(2*z) - S.Half
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def _randrat():
|
| 152 |
+
""" Steer clear of integers. """
|
| 153 |
+
return S(randrange(25) + 10)/50
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def randcplx(offset=-1):
|
| 157 |
+
""" Polys is not good with real coefficients. """
|
| 158 |
+
return _randrat() + I*_randrat() + I*(1 + offset)
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
@slow
|
| 162 |
+
def test_formulae():
|
| 163 |
+
from sympy.simplify.hyperexpand import FormulaCollection
|
| 164 |
+
formulae = FormulaCollection().formulae
|
| 165 |
+
for formula in formulae:
|
| 166 |
+
h = formula.func(formula.z)
|
| 167 |
+
rep = {}
|
| 168 |
+
for n, sym in enumerate(formula.symbols):
|
| 169 |
+
rep[sym] = randcplx(n)
|
| 170 |
+
|
| 171 |
+
# NOTE hyperexpand returns truly branched functions. We know we are
|
| 172 |
+
# on the main sheet, but numerical evaluation can still go wrong
|
| 173 |
+
# (e.g. if exp_polar cannot be evalf'd).
|
| 174 |
+
# Just replace all exp_polar by exp, this usually works.
|
| 175 |
+
|
| 176 |
+
# first test if the closed-form is actually correct
|
| 177 |
+
h = h.subs(rep)
|
| 178 |
+
closed_form = formula.closed_form.subs(rep).rewrite('nonrepsmall')
|
| 179 |
+
z = formula.z
|
| 180 |
+
assert tn(h, closed_form.replace(exp_polar, exp), z)
|
| 181 |
+
|
| 182 |
+
# now test the computed matrix
|
| 183 |
+
cl = (formula.C * formula.B)[0].subs(rep).rewrite('nonrepsmall')
|
| 184 |
+
assert tn(closed_form.replace(
|
| 185 |
+
exp_polar, exp), cl.replace(exp_polar, exp), z)
|
| 186 |
+
deriv1 = z*formula.B.applyfunc(lambda t: t.rewrite(
|
| 187 |
+
'nonrepsmall')).diff(z)
|
| 188 |
+
deriv2 = formula.M * formula.B
|
| 189 |
+
for d1, d2 in zip(deriv1, deriv2):
|
| 190 |
+
assert tn(d1.subs(rep).replace(exp_polar, exp),
|
| 191 |
+
d2.subs(rep).rewrite('nonrepsmall').replace(exp_polar, exp), z)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def test_meijerg_formulae():
|
| 195 |
+
from sympy.simplify.hyperexpand import MeijerFormulaCollection
|
| 196 |
+
formulae = MeijerFormulaCollection().formulae
|
| 197 |
+
for sig in formulae:
|
| 198 |
+
for formula in formulae[sig]:
|
| 199 |
+
g = meijerg(formula.func.an, formula.func.ap,
|
| 200 |
+
formula.func.bm, formula.func.bq,
|
| 201 |
+
formula.z)
|
| 202 |
+
rep = {}
|
| 203 |
+
for sym in formula.symbols:
|
| 204 |
+
rep[sym] = randcplx()
|
| 205 |
+
|
| 206 |
+
# first test if the closed-form is actually correct
|
| 207 |
+
g = g.subs(rep)
|
| 208 |
+
closed_form = formula.closed_form.subs(rep)
|
| 209 |
+
z = formula.z
|
| 210 |
+
assert tn(g, closed_form, z)
|
| 211 |
+
|
| 212 |
+
# now test the computed matrix
|
| 213 |
+
cl = (formula.C * formula.B)[0].subs(rep)
|
| 214 |
+
assert tn(closed_form, cl, z)
|
| 215 |
+
deriv1 = z*formula.B.diff(z)
|
| 216 |
+
deriv2 = formula.M * formula.B
|
| 217 |
+
for d1, d2 in zip(deriv1, deriv2):
|
| 218 |
+
assert tn(d1.subs(rep), d2.subs(rep), z)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def op(f):
|
| 222 |
+
return z*f.diff(z)
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def test_plan():
|
| 226 |
+
assert devise_plan(Hyper_Function([0], ()),
|
| 227 |
+
Hyper_Function([0], ()), z) == []
|
| 228 |
+
with raises(ValueError):
|
| 229 |
+
devise_plan(Hyper_Function([1], ()), Hyper_Function((), ()), z)
|
| 230 |
+
with raises(ValueError):
|
| 231 |
+
devise_plan(Hyper_Function([2], [1]), Hyper_Function([2], [2]), z)
|
| 232 |
+
with raises(ValueError):
|
| 233 |
+
devise_plan(Hyper_Function([2], []), Hyper_Function([S("1/2")], []), z)
|
| 234 |
+
|
| 235 |
+
# We cannot use pi/(10000 + n) because polys is insanely slow.
|
| 236 |
+
a1, a2, b1 = (randcplx(n) for n in range(3))
|
| 237 |
+
b1 += 2*I
|
| 238 |
+
h = hyper([a1, a2], [b1], z)
|
| 239 |
+
|
| 240 |
+
h2 = hyper((a1 + 1, a2), [b1], z)
|
| 241 |
+
assert tn(apply_operators(h,
|
| 242 |
+
devise_plan(Hyper_Function((a1 + 1, a2), [b1]),
|
| 243 |
+
Hyper_Function((a1, a2), [b1]), z), op),
|
| 244 |
+
h2, z)
|
| 245 |
+
|
| 246 |
+
h2 = hyper((a1 + 1, a2 - 1), [b1], z)
|
| 247 |
+
assert tn(apply_operators(h,
|
| 248 |
+
devise_plan(Hyper_Function((a1 + 1, a2 - 1), [b1]),
|
| 249 |
+
Hyper_Function((a1, a2), [b1]), z), op),
|
| 250 |
+
h2, z)
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def test_plan_derivatives():
|
| 254 |
+
a1, a2, a3 = 1, 2, S('1/2')
|
| 255 |
+
b1, b2 = 3, S('5/2')
|
| 256 |
+
h = Hyper_Function((a1, a2, a3), (b1, b2))
|
| 257 |
+
h2 = Hyper_Function((a1 + 1, a2 + 1, a3 + 2), (b1 + 1, b2 + 1))
|
| 258 |
+
ops = devise_plan(h2, h, z)
|
| 259 |
+
f = Formula(h, z, h(z), [])
|
| 260 |
+
deriv = make_derivative_operator(f.M, z)
|
| 261 |
+
assert tn((apply_operators(f.C, ops, deriv)*f.B)[0], h2(z), z)
|
| 262 |
+
|
| 263 |
+
h2 = Hyper_Function((a1, a2 - 1, a3 - 2), (b1 - 1, b2 - 1))
|
| 264 |
+
ops = devise_plan(h2, h, z)
|
| 265 |
+
assert tn((apply_operators(f.C, ops, deriv)*f.B)[0], h2(z), z)
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def test_reduction_operators():
|
| 269 |
+
a1, a2, b1 = (randcplx(n) for n in range(3))
|
| 270 |
+
h = hyper([a1], [b1], z)
|
| 271 |
+
|
| 272 |
+
assert ReduceOrder(2, 0) is None
|
| 273 |
+
assert ReduceOrder(2, -1) is None
|
| 274 |
+
assert ReduceOrder(1, S('1/2')) is None
|
| 275 |
+
|
| 276 |
+
h2 = hyper((a1, a2), (b1, a2), z)
|
| 277 |
+
assert tn(ReduceOrder(a2, a2).apply(h, op), h2, z)
|
| 278 |
+
|
| 279 |
+
h2 = hyper((a1, a2 + 1), (b1, a2), z)
|
| 280 |
+
assert tn(ReduceOrder(a2 + 1, a2).apply(h, op), h2, z)
|
| 281 |
+
|
| 282 |
+
h2 = hyper((a2 + 4, a1), (b1, a2), z)
|
| 283 |
+
assert tn(ReduceOrder(a2 + 4, a2).apply(h, op), h2, z)
|
| 284 |
+
|
| 285 |
+
# test several step order reduction
|
| 286 |
+
ap = (a2 + 4, a1, b1 + 1)
|
| 287 |
+
bq = (a2, b1, b1)
|
| 288 |
+
func, ops = reduce_order(Hyper_Function(ap, bq))
|
| 289 |
+
assert func.ap == (a1,)
|
| 290 |
+
assert func.bq == (b1,)
|
| 291 |
+
assert tn(apply_operators(h, ops, op), hyper(ap, bq, z), z)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
def test_shift_operators():
|
| 295 |
+
a1, a2, b1, b2, b3 = (randcplx(n) for n in range(5))
|
| 296 |
+
h = hyper((a1, a2), (b1, b2, b3), z)
|
| 297 |
+
|
| 298 |
+
raises(ValueError, lambda: ShiftA(0))
|
| 299 |
+
raises(ValueError, lambda: ShiftB(1))
|
| 300 |
+
|
| 301 |
+
assert tn(ShiftA(a1).apply(h, op), hyper((a1 + 1, a2), (b1, b2, b3), z), z)
|
| 302 |
+
assert tn(ShiftA(a2).apply(h, op), hyper((a1, a2 + 1), (b1, b2, b3), z), z)
|
| 303 |
+
assert tn(ShiftB(b1).apply(h, op), hyper((a1, a2), (b1 - 1, b2, b3), z), z)
|
| 304 |
+
assert tn(ShiftB(b2).apply(h, op), hyper((a1, a2), (b1, b2 - 1, b3), z), z)
|
| 305 |
+
assert tn(ShiftB(b3).apply(h, op), hyper((a1, a2), (b1, b2, b3 - 1), z), z)
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def test_ushift_operators():
|
| 309 |
+
a1, a2, b1, b2, b3 = (randcplx(n) for n in range(5))
|
| 310 |
+
h = hyper((a1, a2), (b1, b2, b3), z)
|
| 311 |
+
|
| 312 |
+
raises(ValueError, lambda: UnShiftA((1,), (), 0, z))
|
| 313 |
+
raises(ValueError, lambda: UnShiftB((), (-1,), 0, z))
|
| 314 |
+
raises(ValueError, lambda: UnShiftA((1,), (0, -1, 1), 0, z))
|
| 315 |
+
raises(ValueError, lambda: UnShiftB((0, 1), (1,), 0, z))
|
| 316 |
+
|
| 317 |
+
s = UnShiftA((a1, a2), (b1, b2, b3), 0, z)
|
| 318 |
+
assert tn(s.apply(h, op), hyper((a1 - 1, a2), (b1, b2, b3), z), z)
|
| 319 |
+
s = UnShiftA((a1, a2), (b1, b2, b3), 1, z)
|
| 320 |
+
assert tn(s.apply(h, op), hyper((a1, a2 - 1), (b1, b2, b3), z), z)
|
| 321 |
+
|
| 322 |
+
s = UnShiftB((a1, a2), (b1, b2, b3), 0, z)
|
| 323 |
+
assert tn(s.apply(h, op), hyper((a1, a2), (b1 + 1, b2, b3), z), z)
|
| 324 |
+
s = UnShiftB((a1, a2), (b1, b2, b3), 1, z)
|
| 325 |
+
assert tn(s.apply(h, op), hyper((a1, a2), (b1, b2 + 1, b3), z), z)
|
| 326 |
+
s = UnShiftB((a1, a2), (b1, b2, b3), 2, z)
|
| 327 |
+
assert tn(s.apply(h, op), hyper((a1, a2), (b1, b2, b3 + 1), z), z)
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
def can_do_meijer(a1, a2, b1, b2, numeric=True):
|
| 331 |
+
"""
|
| 332 |
+
This helper function tries to hyperexpand() the meijer g-function
|
| 333 |
+
corresponding to the parameters a1, a2, b1, b2.
|
| 334 |
+
It returns False if this expansion still contains g-functions.
|
| 335 |
+
If numeric is True, it also tests the so-obtained formula numerically
|
| 336 |
+
(at random values) and returns False if the test fails.
|
| 337 |
+
Else it returns True.
|
| 338 |
+
"""
|
| 339 |
+
from sympy.core.function import expand
|
| 340 |
+
from sympy.functions.elementary.complexes import unpolarify
|
| 341 |
+
r = hyperexpand(meijerg(a1, a2, b1, b2, z))
|
| 342 |
+
if r.has(meijerg):
|
| 343 |
+
return False
|
| 344 |
+
# NOTE hyperexpand() returns a truly branched function, whereas numerical
|
| 345 |
+
# evaluation only works on the main branch. Since we are evaluating on
|
| 346 |
+
# the main branch, this should not be a problem, but expressions like
|
| 347 |
+
# exp_polar(I*pi/2*x)**a are evaluated incorrectly. We thus have to get
|
| 348 |
+
# rid of them. The expand heuristically does this...
|
| 349 |
+
r = unpolarify(expand(r, force=True, power_base=True, power_exp=False,
|
| 350 |
+
mul=False, log=False, multinomial=False, basic=False))
|
| 351 |
+
|
| 352 |
+
if not numeric:
|
| 353 |
+
return True
|
| 354 |
+
|
| 355 |
+
repl = {}
|
| 356 |
+
for n, ai in enumerate(meijerg(a1, a2, b1, b2, z).free_symbols - {z}):
|
| 357 |
+
repl[ai] = randcplx(n)
|
| 358 |
+
return tn(meijerg(a1, a2, b1, b2, z).subs(repl), r.subs(repl), z)
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
@slow
|
| 362 |
+
def test_meijerg_expand():
|
| 363 |
+
from sympy.simplify.gammasimp import gammasimp
|
| 364 |
+
from sympy.simplify.simplify import simplify
|
| 365 |
+
# from mpmath docs
|
| 366 |
+
assert hyperexpand(meijerg([[], []], [[0], []], -z)) == exp(z)
|
| 367 |
+
|
| 368 |
+
assert hyperexpand(meijerg([[1, 1], []], [[1], [0]], z)) == \
|
| 369 |
+
log(z + 1)
|
| 370 |
+
assert hyperexpand(meijerg([[1, 1], []], [[1], [1]], z)) == \
|
| 371 |
+
z/(z + 1)
|
| 372 |
+
assert hyperexpand(meijerg([[], []], [[S.Half], [0]], (z/2)**2)) \
|
| 373 |
+
== sin(z)/sqrt(pi)
|
| 374 |
+
assert hyperexpand(meijerg([[], []], [[0], [S.Half]], (z/2)**2)) \
|
| 375 |
+
== cos(z)/sqrt(pi)
|
| 376 |
+
assert can_do_meijer([], [a], [a - 1, a - S.Half], [])
|
| 377 |
+
assert can_do_meijer([], [], [a/2], [-a/2], False) # branches...
|
| 378 |
+
assert can_do_meijer([a], [b], [a], [b, a - 1])
|
| 379 |
+
|
| 380 |
+
# wikipedia
|
| 381 |
+
assert hyperexpand(meijerg([1], [], [], [0], z)) == \
|
| 382 |
+
Piecewise((0, abs(z) < 1), (1, abs(1/z) < 1),
|
| 383 |
+
(meijerg([1], [], [], [0], z), True))
|
| 384 |
+
assert hyperexpand(meijerg([], [1], [0], [], z)) == \
|
| 385 |
+
Piecewise((1, abs(z) < 1), (0, abs(1/z) < 1),
|
| 386 |
+
(meijerg([], [1], [0], [], z), True))
|
| 387 |
+
|
| 388 |
+
# The Special Functions and their Approximations
|
| 389 |
+
assert can_do_meijer([], [], [a + b/2], [a, a - b/2, a + S.Half])
|
| 390 |
+
assert can_do_meijer(
|
| 391 |
+
[], [], [a], [b], False) # branches only agree for small z
|
| 392 |
+
assert can_do_meijer([], [S.Half], [a], [-a])
|
| 393 |
+
assert can_do_meijer([], [], [a, b], [])
|
| 394 |
+
assert can_do_meijer([], [], [a, b], [])
|
| 395 |
+
assert can_do_meijer([], [], [a, a + S.Half], [b, b + S.Half])
|
| 396 |
+
assert can_do_meijer([], [], [a, -a], [0, S.Half], False) # dito
|
| 397 |
+
assert can_do_meijer([], [], [a, a + S.Half, b, b + S.Half], [])
|
| 398 |
+
assert can_do_meijer([S.Half], [], [0], [a, -a])
|
| 399 |
+
assert can_do_meijer([S.Half], [], [a], [0, -a], False) # dito
|
| 400 |
+
assert can_do_meijer([], [a - S.Half], [a, b], [a - S.Half], False)
|
| 401 |
+
assert can_do_meijer([], [a + S.Half], [a + b, a - b, a], [], False)
|
| 402 |
+
assert can_do_meijer([a + S.Half], [], [b, 2*a - b, a], [], False)
|
| 403 |
+
|
| 404 |
+
# This for example is actually zero.
|
| 405 |
+
assert can_do_meijer([], [], [], [a, b])
|
| 406 |
+
|
| 407 |
+
# Testing a bug:
|
| 408 |
+
assert hyperexpand(meijerg([0, 2], [], [], [-1, 1], z)) == \
|
| 409 |
+
Piecewise((0, abs(z) < 1),
|
| 410 |
+
(z*(1 - 1/z**2)/2, abs(1/z) < 1),
|
| 411 |
+
(meijerg([0, 2], [], [], [-1, 1], z), True))
|
| 412 |
+
|
| 413 |
+
# Test that the simplest possible answer is returned:
|
| 414 |
+
assert gammasimp(simplify(hyperexpand(
|
| 415 |
+
meijerg([1], [1 - a], [-a/2, -a/2 + S.Half], [], 1/z)))) == \
|
| 416 |
+
-2*sqrt(pi)*(sqrt(z + 1) + 1)**a/a
|
| 417 |
+
|
| 418 |
+
# Test that hyper is returned
|
| 419 |
+
assert hyperexpand(meijerg([1], [], [a], [0, 0], z)) == hyper(
|
| 420 |
+
(a,), (a + 1, a + 1), z*exp_polar(I*pi))*z**a*gamma(a)/gamma(a + 1)**2
|
| 421 |
+
|
| 422 |
+
# Test place option
|
| 423 |
+
f = meijerg(((0, 1), ()), ((S.Half,), (0,)), z**2)
|
| 424 |
+
assert hyperexpand(f) == sqrt(pi)/sqrt(1 + z**(-2))
|
| 425 |
+
assert hyperexpand(f, place=0) == sqrt(pi)*z/sqrt(z**2 + 1)
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
def test_meijerg_lookup():
|
| 429 |
+
from sympy.functions.special.error_functions import (Ci, Si)
|
| 430 |
+
from sympy.functions.special.gamma_functions import uppergamma
|
| 431 |
+
assert hyperexpand(meijerg([a], [], [b, a], [], z)) == \
|
| 432 |
+
z**b*exp(z)*gamma(-a + b + 1)*uppergamma(a - b, z)
|
| 433 |
+
assert hyperexpand(meijerg([0], [], [0, 0], [], z)) == \
|
| 434 |
+
exp(z)*uppergamma(0, z)
|
| 435 |
+
assert can_do_meijer([a], [], [b, a + 1], [])
|
| 436 |
+
assert can_do_meijer([a], [], [b + 2, a], [])
|
| 437 |
+
assert can_do_meijer([a], [], [b - 2, a], [])
|
| 438 |
+
|
| 439 |
+
assert hyperexpand(meijerg([a], [], [a, a, a - S.Half], [], z)) == \
|
| 440 |
+
-sqrt(pi)*z**(a - S.Half)*(2*cos(2*sqrt(z))*(Si(2*sqrt(z)) - pi/2)
|
| 441 |
+
- 2*sin(2*sqrt(z))*Ci(2*sqrt(z))) == \
|
| 442 |
+
hyperexpand(meijerg([a], [], [a, a - S.Half, a], [], z)) == \
|
| 443 |
+
hyperexpand(meijerg([a], [], [a - S.Half, a, a], [], z))
|
| 444 |
+
assert can_do_meijer([a - 1], [], [a + 2, a - Rational(3, 2), a + 1], [])
|
| 445 |
+
|
| 446 |
+
|
| 447 |
+
@XFAIL
|
| 448 |
+
def test_meijerg_expand_fail():
|
| 449 |
+
# These basically test hyper([], [1/2 - a, 1/2 + 1, 1/2], z),
|
| 450 |
+
# which is *very* messy. But since the meijer g actually yields a
|
| 451 |
+
# sum of bessel functions, things can sometimes be simplified a lot and
|
| 452 |
+
# are then put into tables...
|
| 453 |
+
assert can_do_meijer([], [], [a + S.Half], [a, a - b/2, a + b/2])
|
| 454 |
+
assert can_do_meijer([], [], [0, S.Half], [a, -a])
|
| 455 |
+
assert can_do_meijer([], [], [3*a - S.Half, a, -a - S.Half], [a - S.Half])
|
| 456 |
+
assert can_do_meijer([], [], [0, a - S.Half, -a - S.Half], [S.Half])
|
| 457 |
+
assert can_do_meijer([], [], [a, b + S.Half, b], [2*b - a])
|
| 458 |
+
assert can_do_meijer([], [], [a, b + S.Half, b, 2*b - a])
|
| 459 |
+
assert can_do_meijer([S.Half], [], [-a, a], [0])
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
@slow
|
| 463 |
+
def test_meijerg():
|
| 464 |
+
# carefully set up the parameters.
|
| 465 |
+
# NOTE: this used to fail sometimes. I believe it is fixed, but if you
|
| 466 |
+
# hit an inexplicable test failure here, please let me know the seed.
|
| 467 |
+
a1, a2 = (randcplx(n) - 5*I - n*I for n in range(2))
|
| 468 |
+
b1, b2 = (randcplx(n) + 5*I + n*I for n in range(2))
|
| 469 |
+
b3, b4, b5, a3, a4, a5 = (randcplx() for n in range(6))
|
| 470 |
+
g = meijerg([a1], [a3, a4], [b1], [b3, b4], z)
|
| 471 |
+
|
| 472 |
+
assert ReduceOrder.meijer_minus(3, 4) is None
|
| 473 |
+
assert ReduceOrder.meijer_plus(4, 3) is None
|
| 474 |
+
|
| 475 |
+
g2 = meijerg([a1, a2], [a3, a4], [b1], [b3, b4, a2], z)
|
| 476 |
+
assert tn(ReduceOrder.meijer_plus(a2, a2).apply(g, op), g2, z)
|
| 477 |
+
|
| 478 |
+
g2 = meijerg([a1, a2], [a3, a4], [b1], [b3, b4, a2 + 1], z)
|
| 479 |
+
assert tn(ReduceOrder.meijer_plus(a2, a2 + 1).apply(g, op), g2, z)
|
| 480 |
+
|
| 481 |
+
g2 = meijerg([a1, a2 - 1], [a3, a4], [b1], [b3, b4, a2 + 2], z)
|
| 482 |
+
assert tn(ReduceOrder.meijer_plus(a2 - 1, a2 + 2).apply(g, op), g2, z)
|
| 483 |
+
|
| 484 |
+
g2 = meijerg([a1], [a3, a4, b2 - 1], [b1, b2 + 2], [b3, b4], z)
|
| 485 |
+
assert tn(ReduceOrder.meijer_minus(
|
| 486 |
+
b2 + 2, b2 - 1).apply(g, op), g2, z, tol=1e-6)
|
| 487 |
+
|
| 488 |
+
# test several-step reduction
|
| 489 |
+
an = [a1, a2]
|
| 490 |
+
bq = [b3, b4, a2 + 1]
|
| 491 |
+
ap = [a3, a4, b2 - 1]
|
| 492 |
+
bm = [b1, b2 + 1]
|
| 493 |
+
niq, ops = reduce_order_meijer(G_Function(an, ap, bm, bq))
|
| 494 |
+
assert niq.an == (a1,)
|
| 495 |
+
assert set(niq.ap) == {a3, a4}
|
| 496 |
+
assert niq.bm == (b1,)
|
| 497 |
+
assert set(niq.bq) == {b3, b4}
|
| 498 |
+
assert tn(apply_operators(g, ops, op), meijerg(an, ap, bm, bq, z), z)
|
| 499 |
+
|
| 500 |
+
|
| 501 |
+
def test_meijerg_shift_operators():
|
| 502 |
+
# carefully set up the parameters. XXX this still fails sometimes
|
| 503 |
+
a1, a2, a3, a4, a5, b1, b2, b3, b4, b5 = (randcplx(n) for n in range(10))
|
| 504 |
+
g = meijerg([a1], [a3, a4], [b1], [b3, b4], z)
|
| 505 |
+
|
| 506 |
+
assert tn(MeijerShiftA(b1).apply(g, op),
|
| 507 |
+
meijerg([a1], [a3, a4], [b1 + 1], [b3, b4], z), z)
|
| 508 |
+
assert tn(MeijerShiftB(a1).apply(g, op),
|
| 509 |
+
meijerg([a1 - 1], [a3, a4], [b1], [b3, b4], z), z)
|
| 510 |
+
assert tn(MeijerShiftC(b3).apply(g, op),
|
| 511 |
+
meijerg([a1], [a3, a4], [b1], [b3 + 1, b4], z), z)
|
| 512 |
+
assert tn(MeijerShiftD(a3).apply(g, op),
|
| 513 |
+
meijerg([a1], [a3 - 1, a4], [b1], [b3, b4], z), z)
|
| 514 |
+
|
| 515 |
+
s = MeijerUnShiftA([a1], [a3, a4], [b1], [b3, b4], 0, z)
|
| 516 |
+
assert tn(
|
| 517 |
+
s.apply(g, op), meijerg([a1], [a3, a4], [b1 - 1], [b3, b4], z), z)
|
| 518 |
+
|
| 519 |
+
s = MeijerUnShiftC([a1], [a3, a4], [b1], [b3, b4], 0, z)
|
| 520 |
+
assert tn(
|
| 521 |
+
s.apply(g, op), meijerg([a1], [a3, a4], [b1], [b3 - 1, b4], z), z)
|
| 522 |
+
|
| 523 |
+
s = MeijerUnShiftB([a1], [a3, a4], [b1], [b3, b4], 0, z)
|
| 524 |
+
assert tn(
|
| 525 |
+
s.apply(g, op), meijerg([a1 + 1], [a3, a4], [b1], [b3, b4], z), z)
|
| 526 |
+
|
| 527 |
+
s = MeijerUnShiftD([a1], [a3, a4], [b1], [b3, b4], 0, z)
|
| 528 |
+
assert tn(
|
| 529 |
+
s.apply(g, op), meijerg([a1], [a3 + 1, a4], [b1], [b3, b4], z), z)
|
| 530 |
+
|
| 531 |
+
|
| 532 |
+
@slow
|
| 533 |
+
def test_meijerg_confluence():
|
| 534 |
+
def t(m, a, b):
|
| 535 |
+
from sympy.core.sympify import sympify
|
| 536 |
+
a, b = sympify([a, b])
|
| 537 |
+
m_ = m
|
| 538 |
+
m = hyperexpand(m)
|
| 539 |
+
if not m == Piecewise((a, abs(z) < 1), (b, abs(1/z) < 1), (m_, True)):
|
| 540 |
+
return False
|
| 541 |
+
if not (m.args[0].args[0] == a and m.args[1].args[0] == b):
|
| 542 |
+
return False
|
| 543 |
+
z0 = randcplx()/10
|
| 544 |
+
if abs(m.subs(z, z0).n() - a.subs(z, z0).n()).n() > 1e-10:
|
| 545 |
+
return False
|
| 546 |
+
if abs(m.subs(z, 1/z0).n() - b.subs(z, 1/z0).n()).n() > 1e-10:
|
| 547 |
+
return False
|
| 548 |
+
return True
|
| 549 |
+
|
| 550 |
+
assert t(meijerg([], [1, 1], [0, 0], [], z), -log(z), 0)
|
| 551 |
+
assert t(meijerg(
|
| 552 |
+
[], [3, 1], [0, 0], [], z), -z**2/4 + z - log(z)/2 - Rational(3, 4), 0)
|
| 553 |
+
assert t(meijerg([], [3, 1], [-1, 0], [], z),
|
| 554 |
+
z**2/12 - z/2 + log(z)/2 + Rational(1, 4) + 1/(6*z), 0)
|
| 555 |
+
assert t(meijerg([], [1, 1, 1, 1], [0, 0, 0, 0], [], z), -log(z)**3/6, 0)
|
| 556 |
+
assert t(meijerg([1, 1], [], [], [0, 0], z), 0, -log(1/z))
|
| 557 |
+
assert t(meijerg([1, 1], [2, 2], [1, 1], [0, 0], z),
|
| 558 |
+
-z*log(z) + 2*z, -log(1/z) + 2)
|
| 559 |
+
assert t(meijerg([S.Half], [1, 1], [0, 0], [Rational(3, 2)], z), log(z)/2 - 1, 0)
|
| 560 |
+
|
| 561 |
+
def u(an, ap, bm, bq):
|
| 562 |
+
m = meijerg(an, ap, bm, bq, z)
|
| 563 |
+
m2 = hyperexpand(m, allow_hyper=True)
|
| 564 |
+
if m2.has(meijerg) and not (m2.is_Piecewise and len(m2.args) == 3):
|
| 565 |
+
return False
|
| 566 |
+
return tn(m, m2, z)
|
| 567 |
+
assert u([], [1], [0, 0], [])
|
| 568 |
+
assert u([1, 1], [], [], [0])
|
| 569 |
+
assert u([1, 1], [2, 2, 5], [1, 1, 6], [0, 0])
|
| 570 |
+
assert u([1, 1], [2, 2, 5], [1, 1, 6], [0])
|
| 571 |
+
|
| 572 |
+
|
| 573 |
+
def test_meijerg_with_Floats():
|
| 574 |
+
# see issue #10681
|
| 575 |
+
from sympy.polys.domains.realfield import RR
|
| 576 |
+
f = meijerg(((3.0, 1), ()), ((Rational(3, 2),), (0,)), z)
|
| 577 |
+
a = -2.3632718012073
|
| 578 |
+
g = a*z**Rational(3, 2)*hyper((-0.5, Rational(3, 2)), (Rational(5, 2),), z*exp_polar(I*pi))
|
| 579 |
+
assert RR.almosteq((hyperexpand(f)/g).n(), 1.0, 1e-12)
|
| 580 |
+
|
| 581 |
+
|
| 582 |
+
def test_lerchphi():
|
| 583 |
+
from sympy.functions.special.zeta_functions import (lerchphi, polylog)
|
| 584 |
+
from sympy.simplify.gammasimp import gammasimp
|
| 585 |
+
assert hyperexpand(hyper([1, a], [a + 1], z)/a) == lerchphi(z, 1, a)
|
| 586 |
+
assert hyperexpand(
|
| 587 |
+
hyper([1, a, a], [a + 1, a + 1], z)/a**2) == lerchphi(z, 2, a)
|
| 588 |
+
assert hyperexpand(hyper([1, a, a, a], [a + 1, a + 1, a + 1], z)/a**3) == \
|
| 589 |
+
lerchphi(z, 3, a)
|
| 590 |
+
assert hyperexpand(hyper([1] + [a]*10, [a + 1]*10, z)/a**10) == \
|
| 591 |
+
lerchphi(z, 10, a)
|
| 592 |
+
assert gammasimp(hyperexpand(meijerg([0, 1 - a], [], [0],
|
| 593 |
+
[-a], exp_polar(-I*pi)*z))) == lerchphi(z, 1, a)
|
| 594 |
+
assert gammasimp(hyperexpand(meijerg([0, 1 - a, 1 - a], [], [0],
|
| 595 |
+
[-a, -a], exp_polar(-I*pi)*z))) == lerchphi(z, 2, a)
|
| 596 |
+
assert gammasimp(hyperexpand(meijerg([0, 1 - a, 1 - a, 1 - a], [], [0],
|
| 597 |
+
[-a, -a, -a], exp_polar(-I*pi)*z))) == lerchphi(z, 3, a)
|
| 598 |
+
|
| 599 |
+
assert hyperexpand(z*hyper([1, 1], [2], z)) == -log(1 + -z)
|
| 600 |
+
assert hyperexpand(z*hyper([1, 1, 1], [2, 2], z)) == polylog(2, z)
|
| 601 |
+
assert hyperexpand(z*hyper([1, 1, 1, 1], [2, 2, 2], z)) == polylog(3, z)
|
| 602 |
+
|
| 603 |
+
assert hyperexpand(hyper([1, a, 1 + S.Half], [a + 1, S.Half], z)) == \
|
| 604 |
+
-2*a/(z - 1) + (-2*a**2 + a)*lerchphi(z, 1, a)
|
| 605 |
+
|
| 606 |
+
# Now numerical tests. These make sure reductions etc are carried out
|
| 607 |
+
# correctly
|
| 608 |
+
|
| 609 |
+
# a rational function (polylog at negative integer order)
|
| 610 |
+
assert can_do([2, 2, 2], [1, 1])
|
| 611 |
+
|
| 612 |
+
# NOTE these contain log(1-x) etc ... better make sure we have |z| < 1
|
| 613 |
+
# reduction of order for polylog
|
| 614 |
+
assert can_do([1, 1, 1, b + 5], [2, 2, b], div=10)
|
| 615 |
+
|
| 616 |
+
# reduction of order for lerchphi
|
| 617 |
+
# XXX lerchphi in mpmath is flaky
|
| 618 |
+
assert can_do(
|
| 619 |
+
[1, a, a, a, b + 5], [a + 1, a + 1, a + 1, b], numerical=False)
|
| 620 |
+
|
| 621 |
+
# test a bug
|
| 622 |
+
from sympy.functions.elementary.complexes import Abs
|
| 623 |
+
assert hyperexpand(hyper([S.Half, S.Half, S.Half, 1],
|
| 624 |
+
[Rational(3, 2), Rational(3, 2), Rational(3, 2)], Rational(1, 4))) == \
|
| 625 |
+
Abs(-polylog(3, exp_polar(I*pi)/2) + polylog(3, S.Half))
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
def test_partial_simp():
|
| 629 |
+
# First test that hypergeometric function formulae work.
|
| 630 |
+
a, b, c, d, e = (randcplx() for _ in range(5))
|
| 631 |
+
for func in [Hyper_Function([a, b, c], [d, e]),
|
| 632 |
+
Hyper_Function([], [a, b, c, d, e])]:
|
| 633 |
+
f = build_hypergeometric_formula(func)
|
| 634 |
+
z = f.z
|
| 635 |
+
assert f.closed_form == func(z)
|
| 636 |
+
deriv1 = f.B.diff(z)*z
|
| 637 |
+
deriv2 = f.M*f.B
|
| 638 |
+
for func1, func2 in zip(deriv1, deriv2):
|
| 639 |
+
assert tn(func1, func2, z)
|
| 640 |
+
|
| 641 |
+
# Now test that formulae are partially simplified.
|
| 642 |
+
a, b, z = symbols('a b z')
|
| 643 |
+
assert hyperexpand(hyper([3, a], [1, b], z)) == \
|
| 644 |
+
(-a*b/2 + a*z/2 + 2*a)*hyper([a + 1], [b], z) \
|
| 645 |
+
+ (a*b/2 - 2*a + 1)*hyper([a], [b], z)
|
| 646 |
+
assert tn(
|
| 647 |
+
hyperexpand(hyper([3, d], [1, e], z)), hyper([3, d], [1, e], z), z)
|
| 648 |
+
assert hyperexpand(hyper([3], [1, a, b], z)) == \
|
| 649 |
+
hyper((), (a, b), z) \
|
| 650 |
+
+ z*hyper((), (a + 1, b), z)/(2*a) \
|
| 651 |
+
- z*(b - 4)*hyper((), (a + 1, b + 1), z)/(2*a*b)
|
| 652 |
+
assert tn(
|
| 653 |
+
hyperexpand(hyper([3], [1, d, e], z)), hyper([3], [1, d, e], z), z)
|
| 654 |
+
|
| 655 |
+
|
| 656 |
+
def test_hyperexpand_special():
|
| 657 |
+
assert hyperexpand(hyper([a, b], [c], 1)) == \
|
| 658 |
+
gamma(c)*gamma(c - a - b)/gamma(c - a)/gamma(c - b)
|
| 659 |
+
assert hyperexpand(hyper([a, b], [1 + a - b], -1)) == \
|
| 660 |
+
gamma(1 + a/2)*gamma(1 + a - b)/gamma(1 + a)/gamma(1 + a/2 - b)
|
| 661 |
+
assert hyperexpand(hyper([a, b], [1 + b - a], -1)) == \
|
| 662 |
+
gamma(1 + b/2)*gamma(1 + b - a)/gamma(1 + b)/gamma(1 + b/2 - a)
|
| 663 |
+
assert hyperexpand(meijerg([1 - z - a/2], [1 - z + a/2], [b/2], [-b/2], 1)) == \
|
| 664 |
+
gamma(1 - 2*z)*gamma(z + a/2 + b/2)/gamma(1 - z + a/2 - b/2) \
|
| 665 |
+
/gamma(1 - z - a/2 + b/2)/gamma(1 - z + a/2 + b/2)
|
| 666 |
+
assert hyperexpand(hyper([a], [b], 0)) == 1
|
| 667 |
+
assert hyper([a], [b], 0) != 0
|
| 668 |
+
|
| 669 |
+
|
| 670 |
+
def test_Mod1_behavior():
|
| 671 |
+
from sympy.core.symbol import Symbol
|
| 672 |
+
from sympy.simplify.simplify import simplify
|
| 673 |
+
n = Symbol('n', integer=True)
|
| 674 |
+
# Note: this should not hang.
|
| 675 |
+
assert simplify(hyperexpand(meijerg([1], [], [n + 1], [0], z))) == \
|
| 676 |
+
lowergamma(n + 1, z)
|
| 677 |
+
|
| 678 |
+
|
| 679 |
+
@slow
|
| 680 |
+
def test_prudnikov_misc():
|
| 681 |
+
assert can_do([1, (3 + I)/2, (3 - I)/2], [Rational(3, 2), 2])
|
| 682 |
+
assert can_do([S.Half, a - 1], [Rational(3, 2), a + 1], lowerplane=True)
|
| 683 |
+
assert can_do([], [b + 1])
|
| 684 |
+
assert can_do([a], [a - 1, b + 1])
|
| 685 |
+
|
| 686 |
+
assert can_do([a], [a - S.Half, 2*a])
|
| 687 |
+
assert can_do([a], [a - S.Half, 2*a + 1])
|
| 688 |
+
assert can_do([a], [a - S.Half, 2*a - 1])
|
| 689 |
+
assert can_do([a], [a + S.Half, 2*a])
|
| 690 |
+
assert can_do([a], [a + S.Half, 2*a + 1])
|
| 691 |
+
assert can_do([a], [a + S.Half, 2*a - 1])
|
| 692 |
+
assert can_do([S.Half], [b, 2 - b])
|
| 693 |
+
assert can_do([S.Half], [b, 3 - b])
|
| 694 |
+
assert can_do([1], [2, b])
|
| 695 |
+
|
| 696 |
+
assert can_do([a, a + S.Half], [2*a, b, 2*a - b + 1])
|
| 697 |
+
assert can_do([a, a + S.Half], [S.Half, 2*a, 2*a + S.Half])
|
| 698 |
+
assert can_do([a], [a + 1], lowerplane=True) # lowergamma
|
| 699 |
+
|
| 700 |
+
|
| 701 |
+
def test_prudnikov_1():
|
| 702 |
+
# A. P. Prudnikov, Yu. A. Brychkov and O. I. Marichev (1990).
|
| 703 |
+
# Integrals and Series: More Special Functions, Vol. 3,.
|
| 704 |
+
# Gordon and Breach Science Publisher
|
| 705 |
+
|
| 706 |
+
# 7.3.1
|
| 707 |
+
assert can_do([a, -a], [S.Half])
|
| 708 |
+
assert can_do([a, 1 - a], [S.Half])
|
| 709 |
+
assert can_do([a, 1 - a], [Rational(3, 2)])
|
| 710 |
+
assert can_do([a, 2 - a], [S.Half])
|
| 711 |
+
assert can_do([a, 2 - a], [Rational(3, 2)])
|
| 712 |
+
assert can_do([a, 2 - a], [Rational(3, 2)])
|
| 713 |
+
assert can_do([a, a + S.Half], [2*a - 1])
|
| 714 |
+
assert can_do([a, a + S.Half], [2*a])
|
| 715 |
+
assert can_do([a, a + S.Half], [2*a + 1])
|
| 716 |
+
assert can_do([a, a + S.Half], [S.Half])
|
| 717 |
+
assert can_do([a, a + S.Half], [Rational(3, 2)])
|
| 718 |
+
assert can_do([a, a/2 + 1], [a/2])
|
| 719 |
+
assert can_do([1, b], [2])
|
| 720 |
+
assert can_do([1, b], [b + 1], numerical=False) # Lerch Phi
|
| 721 |
+
# NOTE: branches are complicated for |z| > 1
|
| 722 |
+
|
| 723 |
+
assert can_do([a], [2*a])
|
| 724 |
+
assert can_do([a], [2*a + 1])
|
| 725 |
+
assert can_do([a], [2*a - 1])
|
| 726 |
+
|
| 727 |
+
|
| 728 |
+
@slow
|
| 729 |
+
def test_prudnikov_2():
|
| 730 |
+
h = S.Half
|
| 731 |
+
assert can_do([-h, -h], [h])
|
| 732 |
+
assert can_do([-h, h], [3*h])
|
| 733 |
+
assert can_do([-h, h], [5*h])
|
| 734 |
+
assert can_do([-h, h], [7*h])
|
| 735 |
+
assert can_do([-h, 1], [h])
|
| 736 |
+
|
| 737 |
+
for p in [-h, h]:
|
| 738 |
+
for n in [-h, h, 1, 3*h, 2, 5*h, 3, 7*h, 4]:
|
| 739 |
+
for m in [-h, h, 3*h, 5*h, 7*h]:
|
| 740 |
+
assert can_do([p, n], [m])
|
| 741 |
+
for n in [1, 2, 3, 4]:
|
| 742 |
+
for m in [1, 2, 3, 4]:
|
| 743 |
+
assert can_do([p, n], [m])
|
| 744 |
+
|
| 745 |
+
|
| 746 |
+
def test_prudnikov_3():
|
| 747 |
+
h = S.Half
|
| 748 |
+
assert can_do([Rational(1, 4), Rational(3, 4)], [h])
|
| 749 |
+
assert can_do([Rational(1, 4), Rational(3, 4)], [3*h])
|
| 750 |
+
assert can_do([Rational(1, 3), Rational(2, 3)], [3*h])
|
| 751 |
+
assert can_do([Rational(3, 4), Rational(5, 4)], [h])
|
| 752 |
+
assert can_do([Rational(3, 4), Rational(5, 4)], [3*h])
|
| 753 |
+
|
| 754 |
+
|
| 755 |
+
@tooslow
|
| 756 |
+
def test_prudnikov_3_slow():
|
| 757 |
+
# XXX: This is marked as tooslow and hence skipped in CI. None of the
|
| 758 |
+
# individual cases below fails or hangs. Some cases are slow and the loops
|
| 759 |
+
# below generate 280 different cases. Is it really necessary to test all
|
| 760 |
+
# 280 cases here?
|
| 761 |
+
h = S.Half
|
| 762 |
+
for p in [1, 2, 3, 4]:
|
| 763 |
+
for n in [-h, h, 1, 3*h, 2, 5*h, 3, 7*h, 4, 9*h]:
|
| 764 |
+
for m in [1, 3*h, 2, 5*h, 3, 7*h, 4]:
|
| 765 |
+
assert can_do([p, m], [n])
|
| 766 |
+
|
| 767 |
+
|
| 768 |
+
@slow
|
| 769 |
+
def test_prudnikov_4():
|
| 770 |
+
h = S.Half
|
| 771 |
+
for p in [3*h, 5*h, 7*h]:
|
| 772 |
+
for n in [-h, h, 3*h, 5*h, 7*h]:
|
| 773 |
+
for m in [3*h, 2, 5*h, 3, 7*h, 4]:
|
| 774 |
+
assert can_do([p, m], [n])
|
| 775 |
+
for n in [1, 2, 3, 4]:
|
| 776 |
+
for m in [2, 3, 4]:
|
| 777 |
+
assert can_do([p, m], [n])
|
| 778 |
+
|
| 779 |
+
|
| 780 |
+
@slow
|
| 781 |
+
def test_prudnikov_5():
|
| 782 |
+
h = S.Half
|
| 783 |
+
|
| 784 |
+
for p in [1, 2, 3]:
|
| 785 |
+
for q in range(p, 4):
|
| 786 |
+
for r in [1, 2, 3]:
|
| 787 |
+
for s in range(r, 4):
|
| 788 |
+
assert can_do([-h, p, q], [r, s])
|
| 789 |
+
|
| 790 |
+
for p in [h, 1, 3*h, 2, 5*h, 3]:
|
| 791 |
+
for q in [h, 3*h, 5*h]:
|
| 792 |
+
for r in [h, 3*h, 5*h]:
|
| 793 |
+
for s in [h, 3*h, 5*h]:
|
| 794 |
+
if s <= q and s <= r:
|
| 795 |
+
assert can_do([-h, p, q], [r, s])
|
| 796 |
+
|
| 797 |
+
for p in [h, 1, 3*h, 2, 5*h, 3]:
|
| 798 |
+
for q in [1, 2, 3]:
|
| 799 |
+
for r in [h, 3*h, 5*h]:
|
| 800 |
+
for s in [1, 2, 3]:
|
| 801 |
+
assert can_do([-h, p, q], [r, s])
|
| 802 |
+
|
| 803 |
+
|
| 804 |
+
@slow
|
| 805 |
+
def test_prudnikov_6():
|
| 806 |
+
h = S.Half
|
| 807 |
+
|
| 808 |
+
for m in [3*h, 5*h]:
|
| 809 |
+
for n in [1, 2, 3]:
|
| 810 |
+
for q in [h, 1, 2]:
|
| 811 |
+
for p in [1, 2, 3]:
|
| 812 |
+
assert can_do([h, q, p], [m, n])
|
| 813 |
+
for q in [1, 2, 3]:
|
| 814 |
+
for p in [3*h, 5*h]:
|
| 815 |
+
assert can_do([h, q, p], [m, n])
|
| 816 |
+
|
| 817 |
+
for q in [1, 2]:
|
| 818 |
+
for p in [1, 2, 3]:
|
| 819 |
+
for m in [1, 2, 3]:
|
| 820 |
+
for n in [1, 2, 3]:
|
| 821 |
+
assert can_do([h, q, p], [m, n])
|
| 822 |
+
|
| 823 |
+
assert can_do([h, h, 5*h], [3*h, 3*h])
|
| 824 |
+
assert can_do([h, 1, 5*h], [3*h, 3*h])
|
| 825 |
+
assert can_do([h, 2, 2], [1, 3])
|
| 826 |
+
|
| 827 |
+
# pages 435 to 457 contain more PFDD and stuff like this
|
| 828 |
+
|
| 829 |
+
|
| 830 |
+
@slow
|
| 831 |
+
def test_prudnikov_7():
|
| 832 |
+
assert can_do([3], [6])
|
| 833 |
+
|
| 834 |
+
h = S.Half
|
| 835 |
+
for n in [h, 3*h, 5*h, 7*h]:
|
| 836 |
+
assert can_do([-h], [n])
|
| 837 |
+
for m in [-h, h, 1, 3*h, 2, 5*h, 3, 7*h, 4]: # HERE
|
| 838 |
+
for n in [-h, h, 3*h, 5*h, 7*h, 1, 2, 3, 4]:
|
| 839 |
+
assert can_do([m], [n])
|
| 840 |
+
|
| 841 |
+
|
| 842 |
+
@slow
|
| 843 |
+
def test_prudnikov_8():
|
| 844 |
+
h = S.Half
|
| 845 |
+
|
| 846 |
+
# 7.12.2
|
| 847 |
+
for ai in [1, 2, 3]:
|
| 848 |
+
for bi in [1, 2, 3]:
|
| 849 |
+
for ci in range(1, ai + 1):
|
| 850 |
+
for di in [h, 1, 3*h, 2, 5*h, 3]:
|
| 851 |
+
assert can_do([ai, bi], [ci, di])
|
| 852 |
+
for bi in [3*h, 5*h]:
|
| 853 |
+
for ci in [h, 1, 3*h, 2, 5*h, 3]:
|
| 854 |
+
for di in [1, 2, 3]:
|
| 855 |
+
assert can_do([ai, bi], [ci, di])
|
| 856 |
+
|
| 857 |
+
for ai in [-h, h, 3*h, 5*h]:
|
| 858 |
+
for bi in [1, 2, 3]:
|
| 859 |
+
for ci in [h, 1, 3*h, 2, 5*h, 3]:
|
| 860 |
+
for di in [1, 2, 3]:
|
| 861 |
+
assert can_do([ai, bi], [ci, di])
|
| 862 |
+
for bi in [h, 3*h, 5*h]:
|
| 863 |
+
for ci in [h, 3*h, 5*h, 3]:
|
| 864 |
+
for di in [h, 1, 3*h, 2, 5*h, 3]:
|
| 865 |
+
if ci <= bi:
|
| 866 |
+
assert can_do([ai, bi], [ci, di])
|
| 867 |
+
|
| 868 |
+
|
| 869 |
+
def test_prudnikov_9():
|
| 870 |
+
# 7.13.1 [we have a general formula ... so this is a bit pointless]
|
| 871 |
+
for i in range(9):
|
| 872 |
+
assert can_do([], [(S(i) + 1)/2])
|
| 873 |
+
for i in range(5):
|
| 874 |
+
assert can_do([], [-(2*S(i) + 1)/2])
|
| 875 |
+
|
| 876 |
+
|
| 877 |
+
@slow
|
| 878 |
+
def test_prudnikov_10():
|
| 879 |
+
# 7.14.2
|
| 880 |
+
h = S.Half
|
| 881 |
+
for p in [-h, h, 1, 3*h, 2, 5*h, 3, 7*h, 4]:
|
| 882 |
+
for m in [1, 2, 3, 4]:
|
| 883 |
+
for n in range(m, 5):
|
| 884 |
+
assert can_do([p], [m, n])
|
| 885 |
+
|
| 886 |
+
for p in [1, 2, 3, 4]:
|
| 887 |
+
for n in [h, 3*h, 5*h, 7*h]:
|
| 888 |
+
for m in [1, 2, 3, 4]:
|
| 889 |
+
assert can_do([p], [n, m])
|
| 890 |
+
|
| 891 |
+
for p in [3*h, 5*h, 7*h]:
|
| 892 |
+
for m in [h, 1, 2, 5*h, 3, 7*h, 4]:
|
| 893 |
+
assert can_do([p], [h, m])
|
| 894 |
+
assert can_do([p], [3*h, m])
|
| 895 |
+
|
| 896 |
+
for m in [h, 1, 2, 5*h, 3, 7*h, 4]:
|
| 897 |
+
assert can_do([7*h], [5*h, m])
|
| 898 |
+
|
| 899 |
+
assert can_do([Rational(-1, 2)], [S.Half, S.Half]) # shine-integral shi
|
| 900 |
+
|
| 901 |
+
|
| 902 |
+
def test_prudnikov_11():
|
| 903 |
+
# 7.15
|
| 904 |
+
assert can_do([a, a + S.Half], [2*a, b, 2*a - b])
|
| 905 |
+
assert can_do([a, a + S.Half], [Rational(3, 2), 2*a, 2*a - S.Half])
|
| 906 |
+
|
| 907 |
+
assert can_do([Rational(1, 4), Rational(3, 4)], [S.Half, S.Half, 1])
|
| 908 |
+
assert can_do([Rational(5, 4), Rational(3, 4)], [Rational(3, 2), S.Half, 2])
|
| 909 |
+
assert can_do([Rational(5, 4), Rational(3, 4)], [Rational(3, 2), Rational(3, 2), 1])
|
| 910 |
+
assert can_do([Rational(5, 4), Rational(7, 4)], [Rational(3, 2), Rational(5, 2), 2])
|
| 911 |
+
|
| 912 |
+
assert can_do([1, 1], [Rational(3, 2), 2, 2]) # cosh-integral chi
|
| 913 |
+
|
| 914 |
+
|
| 915 |
+
def test_prudnikov_12():
|
| 916 |
+
# 7.16
|
| 917 |
+
assert can_do(
|
| 918 |
+
[], [a, a + S.Half, 2*a], False) # branches only agree for some z!
|
| 919 |
+
assert can_do([], [a, a + S.Half, 2*a + 1], False) # dito
|
| 920 |
+
assert can_do([], [S.Half, a, a + S.Half])
|
| 921 |
+
assert can_do([], [Rational(3, 2), a, a + S.Half])
|
| 922 |
+
|
| 923 |
+
assert can_do([], [Rational(1, 4), S.Half, Rational(3, 4)])
|
| 924 |
+
assert can_do([], [S.Half, S.Half, 1])
|
| 925 |
+
assert can_do([], [S.Half, Rational(3, 2), 1])
|
| 926 |
+
assert can_do([], [Rational(3, 4), Rational(3, 2), Rational(5, 4)])
|
| 927 |
+
assert can_do([], [1, 1, Rational(3, 2)])
|
| 928 |
+
assert can_do([], [1, 2, Rational(3, 2)])
|
| 929 |
+
assert can_do([], [1, Rational(3, 2), Rational(3, 2)])
|
| 930 |
+
assert can_do([], [Rational(5, 4), Rational(3, 2), Rational(7, 4)])
|
| 931 |
+
assert can_do([], [2, Rational(3, 2), Rational(3, 2)])
|
| 932 |
+
|
| 933 |
+
|
| 934 |
+
@slow
|
| 935 |
+
def test_prudnikov_2F1():
|
| 936 |
+
h = S.Half
|
| 937 |
+
# Elliptic integrals
|
| 938 |
+
for p in [-h, h]:
|
| 939 |
+
for m in [h, 3*h, 5*h, 7*h]:
|
| 940 |
+
for n in [1, 2, 3, 4]:
|
| 941 |
+
assert can_do([p, m], [n])
|
| 942 |
+
|
| 943 |
+
|
| 944 |
+
@XFAIL
|
| 945 |
+
def test_prudnikov_fail_2F1():
|
| 946 |
+
assert can_do([a, b], [b + 1]) # incomplete beta function
|
| 947 |
+
assert can_do([-1, b], [c]) # Poly. also -2, -3 etc
|
| 948 |
+
|
| 949 |
+
# TODO polys
|
| 950 |
+
|
| 951 |
+
# Legendre functions:
|
| 952 |
+
assert can_do([a, b], [a + b + S.Half])
|
| 953 |
+
assert can_do([a, b], [a + b - S.Half])
|
| 954 |
+
assert can_do([a, b], [a + b + Rational(3, 2)])
|
| 955 |
+
assert can_do([a, b], [(a + b + 1)/2])
|
| 956 |
+
assert can_do([a, b], [(a + b)/2 + 1])
|
| 957 |
+
assert can_do([a, b], [a - b + 1])
|
| 958 |
+
assert can_do([a, b], [a - b + 2])
|
| 959 |
+
assert can_do([a, b], [2*b])
|
| 960 |
+
assert can_do([a, b], [S.Half])
|
| 961 |
+
assert can_do([a, b], [Rational(3, 2)])
|
| 962 |
+
assert can_do([a, 1 - a], [c])
|
| 963 |
+
assert can_do([a, 2 - a], [c])
|
| 964 |
+
assert can_do([a, 3 - a], [c])
|
| 965 |
+
assert can_do([a, a + S.Half], [c])
|
| 966 |
+
assert can_do([1, b], [c])
|
| 967 |
+
assert can_do([1, b], [Rational(3, 2)])
|
| 968 |
+
|
| 969 |
+
assert can_do([Rational(1, 4), Rational(3, 4)], [1])
|
| 970 |
+
|
| 971 |
+
# PFDD
|
| 972 |
+
o = S.One
|
| 973 |
+
assert can_do([o/8, 1], [o/8*9])
|
| 974 |
+
assert can_do([o/6, 1], [o/6*7])
|
| 975 |
+
assert can_do([o/6, 1], [o/6*13])
|
| 976 |
+
assert can_do([o/5, 1], [o/5*6])
|
| 977 |
+
assert can_do([o/5, 1], [o/5*11])
|
| 978 |
+
assert can_do([o/4, 1], [o/4*5])
|
| 979 |
+
assert can_do([o/4, 1], [o/4*9])
|
| 980 |
+
assert can_do([o/3, 1], [o/3*4])
|
| 981 |
+
assert can_do([o/3, 1], [o/3*7])
|
| 982 |
+
assert can_do([o/8*3, 1], [o/8*11])
|
| 983 |
+
assert can_do([o/5*2, 1], [o/5*7])
|
| 984 |
+
assert can_do([o/5*2, 1], [o/5*12])
|
| 985 |
+
assert can_do([o/5*3, 1], [o/5*8])
|
| 986 |
+
assert can_do([o/5*3, 1], [o/5*13])
|
| 987 |
+
assert can_do([o/8*5, 1], [o/8*13])
|
| 988 |
+
assert can_do([o/4*3, 1], [o/4*7])
|
| 989 |
+
assert can_do([o/4*3, 1], [o/4*11])
|
| 990 |
+
assert can_do([o/3*2, 1], [o/3*5])
|
| 991 |
+
assert can_do([o/3*2, 1], [o/3*8])
|
| 992 |
+
assert can_do([o/5*4, 1], [o/5*9])
|
| 993 |
+
assert can_do([o/5*4, 1], [o/5*14])
|
| 994 |
+
assert can_do([o/6*5, 1], [o/6*11])
|
| 995 |
+
assert can_do([o/6*5, 1], [o/6*17])
|
| 996 |
+
assert can_do([o/8*7, 1], [o/8*15])
|
| 997 |
+
|
| 998 |
+
|
| 999 |
+
@XFAIL
|
| 1000 |
+
def test_prudnikov_fail_3F2():
|
| 1001 |
+
assert can_do([a, a + Rational(1, 3), a + Rational(2, 3)], [Rational(1, 3), Rational(2, 3)])
|
| 1002 |
+
assert can_do([a, a + Rational(1, 3), a + Rational(2, 3)], [Rational(2, 3), Rational(4, 3)])
|
| 1003 |
+
assert can_do([a, a + Rational(1, 3), a + Rational(2, 3)], [Rational(4, 3), Rational(5, 3)])
|
| 1004 |
+
|
| 1005 |
+
# page 421
|
| 1006 |
+
assert can_do([a, a + Rational(1, 3), a + Rational(2, 3)], [a*Rational(3, 2), (3*a + 1)/2])
|
| 1007 |
+
|
| 1008 |
+
# pages 422 ...
|
| 1009 |
+
assert can_do([Rational(-1, 2), S.Half, S.Half], [1, 1]) # elliptic integrals
|
| 1010 |
+
assert can_do([Rational(-1, 2), S.Half, 1], [Rational(3, 2), Rational(3, 2)])
|
| 1011 |
+
# TODO LOTS more
|
| 1012 |
+
|
| 1013 |
+
# PFDD
|
| 1014 |
+
assert can_do([Rational(1, 8), Rational(3, 8), 1], [Rational(9, 8), Rational(11, 8)])
|
| 1015 |
+
assert can_do([Rational(1, 8), Rational(5, 8), 1], [Rational(9, 8), Rational(13, 8)])
|
| 1016 |
+
assert can_do([Rational(1, 8), Rational(7, 8), 1], [Rational(9, 8), Rational(15, 8)])
|
| 1017 |
+
assert can_do([Rational(1, 6), Rational(1, 3), 1], [Rational(7, 6), Rational(4, 3)])
|
| 1018 |
+
assert can_do([Rational(1, 6), Rational(2, 3), 1], [Rational(7, 6), Rational(5, 3)])
|
| 1019 |
+
assert can_do([Rational(1, 6), Rational(2, 3), 1], [Rational(5, 3), Rational(13, 6)])
|
| 1020 |
+
assert can_do([S.Half, 1, 1], [Rational(1, 4), Rational(3, 4)])
|
| 1021 |
+
# LOTS more
|
| 1022 |
+
|
| 1023 |
+
|
| 1024 |
+
@XFAIL
|
| 1025 |
+
def test_prudnikov_fail_other():
|
| 1026 |
+
# 7.11.2
|
| 1027 |
+
|
| 1028 |
+
# 7.12.1
|
| 1029 |
+
assert can_do([1, a], [b, 1 - 2*a + b]) # ???
|
| 1030 |
+
|
| 1031 |
+
# 7.14.2
|
| 1032 |
+
assert can_do([Rational(-1, 2)], [S.Half, 1]) # struve
|
| 1033 |
+
assert can_do([1], [S.Half, S.Half]) # struve
|
| 1034 |
+
assert can_do([Rational(1, 4)], [S.Half, Rational(5, 4)]) # PFDD
|
| 1035 |
+
assert can_do([Rational(3, 4)], [Rational(3, 2), Rational(7, 4)]) # PFDD
|
| 1036 |
+
assert can_do([1], [Rational(1, 4), Rational(3, 4)]) # PFDD
|
| 1037 |
+
assert can_do([1], [Rational(3, 4), Rational(5, 4)]) # PFDD
|
| 1038 |
+
assert can_do([1], [Rational(5, 4), Rational(7, 4)]) # PFDD
|
| 1039 |
+
# TODO LOTS more
|
| 1040 |
+
|
| 1041 |
+
# 7.15.2
|
| 1042 |
+
assert can_do([S.Half, 1], [Rational(3, 4), Rational(5, 4), Rational(3, 2)]) # PFDD
|
| 1043 |
+
assert can_do([S.Half, 1], [Rational(7, 4), Rational(5, 4), Rational(3, 2)]) # PFDD
|
| 1044 |
+
|
| 1045 |
+
# 7.16.1
|
| 1046 |
+
assert can_do([], [Rational(1, 3), S(2/3)]) # PFDD
|
| 1047 |
+
assert can_do([], [Rational(2, 3), S(4/3)]) # PFDD
|
| 1048 |
+
assert can_do([], [Rational(5, 3), S(4/3)]) # PFDD
|
| 1049 |
+
|
| 1050 |
+
# XXX this does not *evaluate* right??
|
| 1051 |
+
assert can_do([], [a, a + S.Half, 2*a - 1])
|
| 1052 |
+
|
| 1053 |
+
|
| 1054 |
+
def test_bug():
|
| 1055 |
+
h = hyper([-1, 1], [z], -1)
|
| 1056 |
+
assert hyperexpand(h) == (z + 1)/z
|
| 1057 |
+
|
| 1058 |
+
|
| 1059 |
+
def test_omgissue_203():
|
| 1060 |
+
h = hyper((-5, -3, -4), (-6, -6), 1)
|
| 1061 |
+
assert hyperexpand(h) == Rational(1, 30)
|
| 1062 |
+
h = hyper((-6, -7, -5), (-6, -6), 1)
|
| 1063 |
+
assert hyperexpand(h) == Rational(-1, 6)
|
lib/python3.12/site-packages/sympy/simplify/tests/test_powsimp.py
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.function import Function
|
| 2 |
+
from sympy.core.mul import Mul
|
| 3 |
+
from sympy.core.numbers import (E, I, Rational, oo, pi)
|
| 4 |
+
from sympy.core.singleton import S
|
| 5 |
+
from sympy.core.symbol import (Dummy, Symbol, symbols)
|
| 6 |
+
from sympy.functions.elementary.exponential import (exp, log)
|
| 7 |
+
from sympy.functions.elementary.miscellaneous import (root, sqrt)
|
| 8 |
+
from sympy.functions.elementary.trigonometric import sin
|
| 9 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 10 |
+
from sympy.functions.special.hyper import hyper
|
| 11 |
+
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
| 12 |
+
from sympy.simplify.powsimp import (powdenest, powsimp)
|
| 13 |
+
from sympy.simplify.simplify import (signsimp, simplify)
|
| 14 |
+
from sympy.core.symbol import Str
|
| 15 |
+
|
| 16 |
+
from sympy.abc import x, y, z, a, b
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def test_powsimp():
|
| 20 |
+
x, y, z, n = symbols('x,y,z,n')
|
| 21 |
+
f = Function('f')
|
| 22 |
+
assert powsimp( 4**x * 2**(-x) * 2**(-x) ) == 1
|
| 23 |
+
assert powsimp( (-4)**x * (-2)**(-x) * 2**(-x) ) == 1
|
| 24 |
+
|
| 25 |
+
assert powsimp(
|
| 26 |
+
f(4**x * 2**(-x) * 2**(-x)) ) == f(4**x * 2**(-x) * 2**(-x))
|
| 27 |
+
assert powsimp( f(4**x * 2**(-x) * 2**(-x)), deep=True ) == f(1)
|
| 28 |
+
assert exp(x)*exp(y) == exp(x)*exp(y)
|
| 29 |
+
assert powsimp(exp(x)*exp(y)) == exp(x + y)
|
| 30 |
+
assert powsimp(exp(x)*exp(y)*2**x*2**y) == (2*E)**(x + y)
|
| 31 |
+
assert powsimp(exp(x)*exp(y)*2**x*2**y, combine='exp') == \
|
| 32 |
+
exp(x + y)*2**(x + y)
|
| 33 |
+
assert powsimp(exp(x)*exp(y)*exp(2)*sin(x) + sin(y) + 2**x*2**y) == \
|
| 34 |
+
exp(2 + x + y)*sin(x) + sin(y) + 2**(x + y)
|
| 35 |
+
assert powsimp(sin(exp(x)*exp(y))) == sin(exp(x)*exp(y))
|
| 36 |
+
assert powsimp(sin(exp(x)*exp(y)), deep=True) == sin(exp(x + y))
|
| 37 |
+
assert powsimp(x**2*x**y) == x**(2 + y)
|
| 38 |
+
# This should remain factored, because 'exp' with deep=True is supposed
|
| 39 |
+
# to act like old automatic exponent combining.
|
| 40 |
+
assert powsimp((1 + E*exp(E))*exp(-E), combine='exp', deep=True) == \
|
| 41 |
+
(1 + exp(1 + E))*exp(-E)
|
| 42 |
+
assert powsimp((1 + E*exp(E))*exp(-E), deep=True) == \
|
| 43 |
+
(1 + exp(1 + E))*exp(-E)
|
| 44 |
+
assert powsimp((1 + E*exp(E))*exp(-E)) == (1 + exp(1 + E))*exp(-E)
|
| 45 |
+
assert powsimp((1 + E*exp(E))*exp(-E), combine='exp') == \
|
| 46 |
+
(1 + exp(1 + E))*exp(-E)
|
| 47 |
+
assert powsimp((1 + E*exp(E))*exp(-E), combine='base') == \
|
| 48 |
+
(1 + E*exp(E))*exp(-E)
|
| 49 |
+
x, y = symbols('x,y', nonnegative=True)
|
| 50 |
+
n = Symbol('n', real=True)
|
| 51 |
+
assert powsimp(y**n * (y/x)**(-n)) == x**n
|
| 52 |
+
assert powsimp(x**(x**(x*y)*y**(x*y))*y**(x**(x*y)*y**(x*y)), deep=True) \
|
| 53 |
+
== (x*y)**(x*y)**(x*y)
|
| 54 |
+
assert powsimp(2**(2**(2*x)*x), deep=False) == 2**(2**(2*x)*x)
|
| 55 |
+
assert powsimp(2**(2**(2*x)*x), deep=True) == 2**(x*4**x)
|
| 56 |
+
assert powsimp(
|
| 57 |
+
exp(-x + exp(-x)*exp(-x*log(x))), deep=False, combine='exp') == \
|
| 58 |
+
exp(-x + exp(-x)*exp(-x*log(x)))
|
| 59 |
+
assert powsimp(
|
| 60 |
+
exp(-x + exp(-x)*exp(-x*log(x))), deep=False, combine='exp') == \
|
| 61 |
+
exp(-x + exp(-x)*exp(-x*log(x)))
|
| 62 |
+
assert powsimp((x + y)/(3*z), deep=False, combine='exp') == (x + y)/(3*z)
|
| 63 |
+
assert powsimp((x/3 + y/3)/z, deep=True, combine='exp') == (x/3 + y/3)/z
|
| 64 |
+
assert powsimp(exp(x)/(1 + exp(x)*exp(y)), deep=True) == \
|
| 65 |
+
exp(x)/(1 + exp(x + y))
|
| 66 |
+
assert powsimp(x*y**(z**x*z**y), deep=True) == x*y**(z**(x + y))
|
| 67 |
+
assert powsimp((z**x*z**y)**x, deep=True) == (z**(x + y))**x
|
| 68 |
+
assert powsimp(x*(z**x*z**y)**x, deep=True) == x*(z**(x + y))**x
|
| 69 |
+
p = symbols('p', positive=True)
|
| 70 |
+
assert powsimp((1/x)**log(2)/x) == (1/x)**(1 + log(2))
|
| 71 |
+
assert powsimp((1/p)**log(2)/p) == p**(-1 - log(2))
|
| 72 |
+
|
| 73 |
+
# coefficient of exponent can only be simplified for positive bases
|
| 74 |
+
assert powsimp(2**(2*x)) == 4**x
|
| 75 |
+
assert powsimp((-1)**(2*x)) == (-1)**(2*x)
|
| 76 |
+
i = symbols('i', integer=True)
|
| 77 |
+
assert powsimp((-1)**(2*i)) == 1
|
| 78 |
+
assert powsimp((-1)**(-x)) != (-1)**x # could be 1/((-1)**x), but is not
|
| 79 |
+
# force=True overrides assumptions
|
| 80 |
+
assert powsimp((-1)**(2*x), force=True) == 1
|
| 81 |
+
|
| 82 |
+
# rational exponents allow combining of negative terms
|
| 83 |
+
w, n, m = symbols('w n m', negative=True)
|
| 84 |
+
e = i/a # not a rational exponent if `a` is unknown
|
| 85 |
+
ex = w**e*n**e*m**e
|
| 86 |
+
assert powsimp(ex) == m**(i/a)*n**(i/a)*w**(i/a)
|
| 87 |
+
e = i/3
|
| 88 |
+
ex = w**e*n**e*m**e
|
| 89 |
+
assert powsimp(ex) == (-1)**i*(-m*n*w)**(i/3)
|
| 90 |
+
e = (3 + i)/i
|
| 91 |
+
ex = w**e*n**e*m**e
|
| 92 |
+
assert powsimp(ex) == (-1)**(3*e)*(-m*n*w)**e
|
| 93 |
+
|
| 94 |
+
eq = x**(a*Rational(2, 3))
|
| 95 |
+
# eq != (x**a)**(2/3) (try x = -1 and a = 3 to see)
|
| 96 |
+
assert powsimp(eq).exp == eq.exp == a*Rational(2, 3)
|
| 97 |
+
# powdenest goes the other direction
|
| 98 |
+
assert powsimp(2**(2*x)) == 4**x
|
| 99 |
+
|
| 100 |
+
assert powsimp(exp(p/2)) == exp(p/2)
|
| 101 |
+
|
| 102 |
+
# issue 6368
|
| 103 |
+
eq = Mul(*[sqrt(Dummy(imaginary=True)) for i in range(3)])
|
| 104 |
+
assert powsimp(eq) == eq and eq.is_Mul
|
| 105 |
+
|
| 106 |
+
assert all(powsimp(e) == e for e in (sqrt(x**a), sqrt(x**2)))
|
| 107 |
+
|
| 108 |
+
# issue 8836
|
| 109 |
+
assert str( powsimp(exp(I*pi/3)*root(-1,3)) ) == '(-1)**(2/3)'
|
| 110 |
+
|
| 111 |
+
# issue 9183
|
| 112 |
+
assert powsimp(-0.1**x) == -0.1**x
|
| 113 |
+
|
| 114 |
+
# issue 10095
|
| 115 |
+
assert powsimp((1/(2*E))**oo) == (exp(-1)/2)**oo
|
| 116 |
+
|
| 117 |
+
# PR 13131
|
| 118 |
+
eq = sin(2*x)**2*sin(2.0*x)**2
|
| 119 |
+
assert powsimp(eq) == eq
|
| 120 |
+
|
| 121 |
+
# issue 14615
|
| 122 |
+
assert powsimp(x**2*y**3*(x*y**2)**Rational(3, 2)
|
| 123 |
+
) == x*y*(x*y**2)**Rational(5, 2)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def test_powsimp_negated_base():
|
| 127 |
+
assert powsimp((-x + y)/sqrt(x - y)) == -sqrt(x - y)
|
| 128 |
+
assert powsimp((-x + y)*(-z + y)/sqrt(x - y)/sqrt(z - y)) == sqrt(x - y)*sqrt(z - y)
|
| 129 |
+
p = symbols('p', positive=True)
|
| 130 |
+
reps = {p: 2, a: S.Half}
|
| 131 |
+
assert powsimp((-p)**a/p**a).subs(reps) == ((-1)**a).subs(reps)
|
| 132 |
+
assert powsimp((-p)**a*p**a).subs(reps) == ((-p**2)**a).subs(reps)
|
| 133 |
+
n = symbols('n', negative=True)
|
| 134 |
+
reps = {p: -2, a: S.Half}
|
| 135 |
+
assert powsimp((-n)**a/n**a).subs(reps) == (-1)**(-a).subs(a, S.Half)
|
| 136 |
+
assert powsimp((-n)**a*n**a).subs(reps) == ((-n**2)**a).subs(reps)
|
| 137 |
+
# if x is 0 then the lhs is 0**a*oo**a which is not (-1)**a
|
| 138 |
+
eq = (-x)**a/x**a
|
| 139 |
+
assert powsimp(eq) == eq
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def test_powsimp_nc():
|
| 143 |
+
x, y, z = symbols('x,y,z')
|
| 144 |
+
A, B, C = symbols('A B C', commutative=False)
|
| 145 |
+
|
| 146 |
+
assert powsimp(A**x*A**y, combine='all') == A**(x + y)
|
| 147 |
+
assert powsimp(A**x*A**y, combine='base') == A**x*A**y
|
| 148 |
+
assert powsimp(A**x*A**y, combine='exp') == A**(x + y)
|
| 149 |
+
|
| 150 |
+
assert powsimp(A**x*B**x, combine='all') == A**x*B**x
|
| 151 |
+
assert powsimp(A**x*B**x, combine='base') == A**x*B**x
|
| 152 |
+
assert powsimp(A**x*B**x, combine='exp') == A**x*B**x
|
| 153 |
+
|
| 154 |
+
assert powsimp(B**x*A**x, combine='all') == B**x*A**x
|
| 155 |
+
assert powsimp(B**x*A**x, combine='base') == B**x*A**x
|
| 156 |
+
assert powsimp(B**x*A**x, combine='exp') == B**x*A**x
|
| 157 |
+
|
| 158 |
+
assert powsimp(A**x*A**y*A**z, combine='all') == A**(x + y + z)
|
| 159 |
+
assert powsimp(A**x*A**y*A**z, combine='base') == A**x*A**y*A**z
|
| 160 |
+
assert powsimp(A**x*A**y*A**z, combine='exp') == A**(x + y + z)
|
| 161 |
+
|
| 162 |
+
assert powsimp(A**x*B**x*C**x, combine='all') == A**x*B**x*C**x
|
| 163 |
+
assert powsimp(A**x*B**x*C**x, combine='base') == A**x*B**x*C**x
|
| 164 |
+
assert powsimp(A**x*B**x*C**x, combine='exp') == A**x*B**x*C**x
|
| 165 |
+
|
| 166 |
+
assert powsimp(B**x*A**x*C**x, combine='all') == B**x*A**x*C**x
|
| 167 |
+
assert powsimp(B**x*A**x*C**x, combine='base') == B**x*A**x*C**x
|
| 168 |
+
assert powsimp(B**x*A**x*C**x, combine='exp') == B**x*A**x*C**x
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def test_issue_6440():
|
| 172 |
+
assert powsimp(16*2**a*8**b) == 2**(a + 3*b + 4)
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def test_powdenest():
|
| 176 |
+
x, y = symbols('x,y')
|
| 177 |
+
p, q = symbols('p q', positive=True)
|
| 178 |
+
i, j = symbols('i,j', integer=True)
|
| 179 |
+
|
| 180 |
+
assert powdenest(x) == x
|
| 181 |
+
assert powdenest(x + 2*(x**(a*Rational(2, 3)))**(3*x)) == (x + 2*(x**(a*Rational(2, 3)))**(3*x))
|
| 182 |
+
assert powdenest((exp(a*Rational(2, 3)))**(3*x)) # -X-> (exp(a/3))**(6*x)
|
| 183 |
+
assert powdenest((x**(a*Rational(2, 3)))**(3*x)) == ((x**(a*Rational(2, 3)))**(3*x))
|
| 184 |
+
assert powdenest(exp(3*x*log(2))) == 2**(3*x)
|
| 185 |
+
assert powdenest(sqrt(p**2)) == p
|
| 186 |
+
eq = p**(2*i)*q**(4*i)
|
| 187 |
+
assert powdenest(eq) == (p*q**2)**(2*i)
|
| 188 |
+
# -X-> (x**x)**i*(x**x)**j == x**(x*(i + j))
|
| 189 |
+
assert powdenest((x**x)**(i + j))
|
| 190 |
+
assert powdenest(exp(3*y*log(x))) == x**(3*y)
|
| 191 |
+
assert powdenest(exp(y*(log(a) + log(b)))) == (a*b)**y
|
| 192 |
+
assert powdenest(exp(3*(log(a) + log(b)))) == a**3*b**3
|
| 193 |
+
assert powdenest(((x**(2*i))**(3*y))**x) == ((x**(2*i))**(3*y))**x
|
| 194 |
+
assert powdenest(((x**(2*i))**(3*y))**x, force=True) == x**(6*i*x*y)
|
| 195 |
+
assert powdenest(((x**(a*Rational(2, 3)))**(3*y/i))**x) == \
|
| 196 |
+
(((x**(a*Rational(2, 3)))**(3*y/i))**x)
|
| 197 |
+
assert powdenest((x**(2*i)*y**(4*i))**z, force=True) == (x*y**2)**(2*i*z)
|
| 198 |
+
assert powdenest((p**(2*i)*q**(4*i))**j) == (p*q**2)**(2*i*j)
|
| 199 |
+
e = ((p**(2*a))**(3*y))**x
|
| 200 |
+
assert powdenest(e) == e
|
| 201 |
+
e = ((x**2*y**4)**a)**(x*y)
|
| 202 |
+
assert powdenest(e) == e
|
| 203 |
+
e = (((x**2*y**4)**a)**(x*y))**3
|
| 204 |
+
assert powdenest(e) == ((x**2*y**4)**a)**(3*x*y)
|
| 205 |
+
assert powdenest((((x**2*y**4)**a)**(x*y)), force=True) == \
|
| 206 |
+
(x*y**2)**(2*a*x*y)
|
| 207 |
+
assert powdenest((((x**2*y**4)**a)**(x*y))**3, force=True) == \
|
| 208 |
+
(x*y**2)**(6*a*x*y)
|
| 209 |
+
assert powdenest((x**2*y**6)**i) != (x*y**3)**(2*i)
|
| 210 |
+
x, y = symbols('x,y', positive=True)
|
| 211 |
+
assert powdenest((x**2*y**6)**i) == (x*y**3)**(2*i)
|
| 212 |
+
|
| 213 |
+
assert powdenest((x**(i*Rational(2, 3))*y**(i/2))**(2*i)) == (x**Rational(4, 3)*y)**(i**2)
|
| 214 |
+
assert powdenest(sqrt(x**(2*i)*y**(6*i))) == (x*y**3)**i
|
| 215 |
+
|
| 216 |
+
assert powdenest(4**x) == 2**(2*x)
|
| 217 |
+
assert powdenest((4**x)**y) == 2**(2*x*y)
|
| 218 |
+
assert powdenest(4**x*y) == 2**(2*x)*y
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def test_powdenest_polar():
|
| 222 |
+
x, y, z = symbols('x y z', polar=True)
|
| 223 |
+
a, b, c = symbols('a b c')
|
| 224 |
+
assert powdenest((x*y*z)**a) == x**a*y**a*z**a
|
| 225 |
+
assert powdenest((x**a*y**b)**c) == x**(a*c)*y**(b*c)
|
| 226 |
+
assert powdenest(((x**a)**b*y**c)**c) == x**(a*b*c)*y**(c**2)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def test_issue_5805():
|
| 230 |
+
arg = ((gamma(x)*hyper((), (), x))*pi)**2
|
| 231 |
+
assert powdenest(arg) == (pi*gamma(x)*hyper((), (), x))**2
|
| 232 |
+
assert arg.is_positive is None
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
def test_issue_9324_powsimp_on_matrix_symbol():
|
| 236 |
+
M = MatrixSymbol('M', 10, 10)
|
| 237 |
+
expr = powsimp(M, deep=True)
|
| 238 |
+
assert expr == M
|
| 239 |
+
assert expr.args[0] == Str('M')
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
def test_issue_6367():
|
| 243 |
+
z = -5*sqrt(2)/(2*sqrt(2*sqrt(29) + 29)) + sqrt(-sqrt(29)/29 + S.Half)
|
| 244 |
+
assert Mul(*[powsimp(a) for a in Mul.make_args(z.normal())]) == 0
|
| 245 |
+
assert powsimp(z.normal()) == 0
|
| 246 |
+
assert simplify(z) == 0
|
| 247 |
+
assert powsimp(sqrt(2 + sqrt(3))*sqrt(2 - sqrt(3)) + 1) == 2
|
| 248 |
+
assert powsimp(z) != 0
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def test_powsimp_polar():
|
| 252 |
+
from sympy.functions.elementary.complexes import polar_lift
|
| 253 |
+
from sympy.functions.elementary.exponential import exp_polar
|
| 254 |
+
x, y, z = symbols('x y z')
|
| 255 |
+
p, q, r = symbols('p q r', polar=True)
|
| 256 |
+
|
| 257 |
+
assert (polar_lift(-1))**(2*x) == exp_polar(2*pi*I*x)
|
| 258 |
+
assert powsimp(p**x * q**x) == (p*q)**x
|
| 259 |
+
assert p**x * (1/p)**x == 1
|
| 260 |
+
assert (1/p)**x == p**(-x)
|
| 261 |
+
|
| 262 |
+
assert exp_polar(x)*exp_polar(y) == exp_polar(x)*exp_polar(y)
|
| 263 |
+
assert powsimp(exp_polar(x)*exp_polar(y)) == exp_polar(x + y)
|
| 264 |
+
assert powsimp(exp_polar(x)*exp_polar(y)*p**x*p**y) == \
|
| 265 |
+
(p*exp_polar(1))**(x + y)
|
| 266 |
+
assert powsimp(exp_polar(x)*exp_polar(y)*p**x*p**y, combine='exp') == \
|
| 267 |
+
exp_polar(x + y)*p**(x + y)
|
| 268 |
+
assert powsimp(
|
| 269 |
+
exp_polar(x)*exp_polar(y)*exp_polar(2)*sin(x) + sin(y) + p**x*p**y) \
|
| 270 |
+
== p**(x + y) + sin(x)*exp_polar(2 + x + y) + sin(y)
|
| 271 |
+
assert powsimp(sin(exp_polar(x)*exp_polar(y))) == \
|
| 272 |
+
sin(exp_polar(x)*exp_polar(y))
|
| 273 |
+
assert powsimp(sin(exp_polar(x)*exp_polar(y)), deep=True) == \
|
| 274 |
+
sin(exp_polar(x + y))
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
def test_issue_5728():
|
| 278 |
+
b = x*sqrt(y)
|
| 279 |
+
a = sqrt(b)
|
| 280 |
+
c = sqrt(sqrt(x)*y)
|
| 281 |
+
assert powsimp(a*b) == sqrt(b)**3
|
| 282 |
+
assert powsimp(a*b**2*sqrt(y)) == sqrt(y)*a**5
|
| 283 |
+
assert powsimp(a*x**2*c**3*y) == c**3*a**5
|
| 284 |
+
assert powsimp(a*x*c**3*y**2) == c**7*a
|
| 285 |
+
assert powsimp(x*c**3*y**2) == c**7
|
| 286 |
+
assert powsimp(x*c**3*y) == x*y*c**3
|
| 287 |
+
assert powsimp(sqrt(x)*c**3*y) == c**5
|
| 288 |
+
assert powsimp(sqrt(x)*a**3*sqrt(y)) == sqrt(x)*sqrt(y)*a**3
|
| 289 |
+
assert powsimp(Mul(sqrt(x)*c**3*sqrt(y), y, evaluate=False)) == \
|
| 290 |
+
sqrt(x)*sqrt(y)**3*c**3
|
| 291 |
+
assert powsimp(a**2*a*x**2*y) == a**7
|
| 292 |
+
|
| 293 |
+
# symbolic powers work, too
|
| 294 |
+
b = x**y*y
|
| 295 |
+
a = b*sqrt(b)
|
| 296 |
+
assert a.is_Mul is True
|
| 297 |
+
assert powsimp(a) == sqrt(b)**3
|
| 298 |
+
|
| 299 |
+
# as does exp
|
| 300 |
+
a = x*exp(y*Rational(2, 3))
|
| 301 |
+
assert powsimp(a*sqrt(a)) == sqrt(a)**3
|
| 302 |
+
assert powsimp(a**2*sqrt(a)) == sqrt(a)**5
|
| 303 |
+
assert powsimp(a**2*sqrt(sqrt(a))) == sqrt(sqrt(a))**9
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
def test_issue_from_PR1599():
|
| 307 |
+
n1, n2, n3, n4 = symbols('n1 n2 n3 n4', negative=True)
|
| 308 |
+
assert (powsimp(sqrt(n1)*sqrt(n2)*sqrt(n3)) ==
|
| 309 |
+
-I*sqrt(-n1)*sqrt(-n2)*sqrt(-n3))
|
| 310 |
+
assert (powsimp(root(n1, 3)*root(n2, 3)*root(n3, 3)*root(n4, 3)) ==
|
| 311 |
+
-(-1)**Rational(1, 3)*
|
| 312 |
+
(-n1)**Rational(1, 3)*(-n2)**Rational(1, 3)*(-n3)**Rational(1, 3)*(-n4)**Rational(1, 3))
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def test_issue_10195():
|
| 316 |
+
a = Symbol('a', integer=True)
|
| 317 |
+
l = Symbol('l', even=True, nonzero=True)
|
| 318 |
+
n = Symbol('n', odd=True)
|
| 319 |
+
e_x = (-1)**(n/2 - S.Half) - (-1)**(n*Rational(3, 2) - S.Half)
|
| 320 |
+
assert powsimp((-1)**(l/2)) == I**l
|
| 321 |
+
assert powsimp((-1)**(n/2)) == I**n
|
| 322 |
+
assert powsimp((-1)**(n*Rational(3, 2))) == -I**n
|
| 323 |
+
assert powsimp(e_x) == (-1)**(n/2 - S.Half) + (-1)**(n*Rational(3, 2) +
|
| 324 |
+
S.Half)
|
| 325 |
+
assert powsimp((-1)**(a*Rational(3, 2))) == (-I)**a
|
| 326 |
+
|
| 327 |
+
def test_issue_15709():
|
| 328 |
+
assert powsimp(3**x*Rational(2, 3)) == 2*3**(x-1)
|
| 329 |
+
assert powsimp(2*3**x/3) == 2*3**(x-1)
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
def test_issue_11981():
|
| 333 |
+
x, y = symbols('x y', commutative=False)
|
| 334 |
+
assert powsimp((x*y)**2 * (y*x)**2) == (x*y)**2 * (y*x)**2
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
def test_issue_17524():
|
| 338 |
+
a = symbols("a", real=True)
|
| 339 |
+
e = (-1 - a**2)*sqrt(1 + a**2)
|
| 340 |
+
assert signsimp(powsimp(e)) == signsimp(e) == -(a**2 + 1)**(S(3)/2)
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def test_issue_19627():
|
| 344 |
+
# if you use force the user must verify
|
| 345 |
+
assert powdenest(sqrt(sin(x)**2), force=True) == sin(x)
|
| 346 |
+
assert powdenest((x**(S.Half/y))**(2*y), force=True) == x
|
| 347 |
+
from sympy.core.function import expand_power_base
|
| 348 |
+
e = 1 - a
|
| 349 |
+
expr = (exp(z/e)*x**(b/e)*y**((1 - b)/e))**e
|
| 350 |
+
assert powdenest(expand_power_base(expr, force=True), force=True
|
| 351 |
+
) == x**b*y**(1 - b)*exp(z)
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def test_issue_22546():
|
| 355 |
+
p1, p2 = symbols('p1, p2', positive=True)
|
| 356 |
+
ref = powsimp(p1**z/p2**z)
|
| 357 |
+
e = z + 1
|
| 358 |
+
ans = ref.subs(z, e)
|
| 359 |
+
assert ans.is_Pow
|
| 360 |
+
assert powsimp(p1**e/p2**e) == ans
|
| 361 |
+
i = symbols('i', integer=True)
|
| 362 |
+
ref = powsimp(x**i/y**i)
|
| 363 |
+
e = i + 1
|
| 364 |
+
ans = ref.subs(i, e)
|
| 365 |
+
assert ans.is_Pow
|
| 366 |
+
assert powsimp(x**e/y**e) == ans
|
lib/python3.12/site-packages/sympy/simplify/tests/test_radsimp.py
ADDED
|
@@ -0,0 +1,490 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.add import Add
|
| 2 |
+
from sympy.core.function import (Derivative, Function, diff)
|
| 3 |
+
from sympy.core.mul import Mul
|
| 4 |
+
from sympy.core.numbers import (I, Rational)
|
| 5 |
+
from sympy.core.power import Pow
|
| 6 |
+
from sympy.core.singleton import S
|
| 7 |
+
from sympy.core.symbol import (Symbol, Wild, symbols)
|
| 8 |
+
from sympy.functions.elementary.complexes import Abs
|
| 9 |
+
from sympy.functions.elementary.exponential import (exp, log)
|
| 10 |
+
from sympy.functions.elementary.miscellaneous import (root, sqrt)
|
| 11 |
+
from sympy.functions.elementary.trigonometric import (cos, sin)
|
| 12 |
+
from sympy.polys.polytools import factor
|
| 13 |
+
from sympy.series.order import O
|
| 14 |
+
from sympy.simplify.radsimp import (collect, collect_const, fraction, radsimp, rcollect)
|
| 15 |
+
|
| 16 |
+
from sympy.core.expr import unchanged
|
| 17 |
+
from sympy.core.mul import _unevaluated_Mul as umul
|
| 18 |
+
from sympy.simplify.radsimp import (_unevaluated_Add,
|
| 19 |
+
collect_sqrt, fraction_expand, collect_abs)
|
| 20 |
+
from sympy.testing.pytest import raises
|
| 21 |
+
|
| 22 |
+
from sympy.abc import x, y, z, a, b, c, d
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_radsimp():
|
| 26 |
+
r2 = sqrt(2)
|
| 27 |
+
r3 = sqrt(3)
|
| 28 |
+
r5 = sqrt(5)
|
| 29 |
+
r7 = sqrt(7)
|
| 30 |
+
assert fraction(radsimp(1/r2)) == (sqrt(2), 2)
|
| 31 |
+
assert radsimp(1/(1 + r2)) == \
|
| 32 |
+
-1 + sqrt(2)
|
| 33 |
+
assert radsimp(1/(r2 + r3)) == \
|
| 34 |
+
-sqrt(2) + sqrt(3)
|
| 35 |
+
assert fraction(radsimp(1/(1 + r2 + r3))) == \
|
| 36 |
+
(-sqrt(6) + sqrt(2) + 2, 4)
|
| 37 |
+
assert fraction(radsimp(1/(r2 + r3 + r5))) == \
|
| 38 |
+
(-sqrt(30) + 2*sqrt(3) + 3*sqrt(2), 12)
|
| 39 |
+
assert fraction(radsimp(1/(1 + r2 + r3 + r5))) == (
|
| 40 |
+
(-34*sqrt(10) - 26*sqrt(15) - 55*sqrt(3) - 61*sqrt(2) + 14*sqrt(30) +
|
| 41 |
+
93 + 46*sqrt(6) + 53*sqrt(5), 71))
|
| 42 |
+
assert fraction(radsimp(1/(r2 + r3 + r5 + r7))) == (
|
| 43 |
+
(-50*sqrt(42) - 133*sqrt(5) - 34*sqrt(70) - 145*sqrt(3) + 22*sqrt(105)
|
| 44 |
+
+ 185*sqrt(2) + 62*sqrt(30) + 135*sqrt(7), 215))
|
| 45 |
+
z = radsimp(1/(1 + r2/3 + r3/5 + r5 + r7))
|
| 46 |
+
assert len((3616791619821680643598*z).args) == 16
|
| 47 |
+
assert radsimp(1/z) == 1/z
|
| 48 |
+
assert radsimp(1/z, max_terms=20).expand() == 1 + r2/3 + r3/5 + r5 + r7
|
| 49 |
+
assert radsimp(1/(r2*3)) == \
|
| 50 |
+
sqrt(2)/6
|
| 51 |
+
assert radsimp(1/(r2*a + r3 + r5 + r7)) == (
|
| 52 |
+
(8*sqrt(2)*a**7 - 8*sqrt(7)*a**6 - 8*sqrt(5)*a**6 - 8*sqrt(3)*a**6 -
|
| 53 |
+
180*sqrt(2)*a**5 + 8*sqrt(30)*a**5 + 8*sqrt(42)*a**5 + 8*sqrt(70)*a**5
|
| 54 |
+
- 24*sqrt(105)*a**4 + 84*sqrt(3)*a**4 + 100*sqrt(5)*a**4 +
|
| 55 |
+
116*sqrt(7)*a**4 - 72*sqrt(70)*a**3 - 40*sqrt(42)*a**3 -
|
| 56 |
+
8*sqrt(30)*a**3 + 782*sqrt(2)*a**3 - 462*sqrt(3)*a**2 -
|
| 57 |
+
302*sqrt(7)*a**2 - 254*sqrt(5)*a**2 + 120*sqrt(105)*a**2 -
|
| 58 |
+
795*sqrt(2)*a - 62*sqrt(30)*a + 82*sqrt(42)*a + 98*sqrt(70)*a -
|
| 59 |
+
118*sqrt(105) + 59*sqrt(7) + 295*sqrt(5) + 531*sqrt(3))/(16*a**8 -
|
| 60 |
+
480*a**6 + 3128*a**4 - 6360*a**2 + 3481))
|
| 61 |
+
assert radsimp(1/(r2*a + r2*b + r3 + r7)) == (
|
| 62 |
+
(sqrt(2)*a*(a + b)**2 - 5*sqrt(2)*a + sqrt(42)*a + sqrt(2)*b*(a +
|
| 63 |
+
b)**2 - 5*sqrt(2)*b + sqrt(42)*b - sqrt(7)*(a + b)**2 - sqrt(3)*(a +
|
| 64 |
+
b)**2 - 2*sqrt(3) + 2*sqrt(7))/(2*a**4 + 8*a**3*b + 12*a**2*b**2 -
|
| 65 |
+
20*a**2 + 8*a*b**3 - 40*a*b + 2*b**4 - 20*b**2 + 8))
|
| 66 |
+
assert radsimp(1/(r2*a + r2*b + r2*c + r2*d)) == \
|
| 67 |
+
sqrt(2)/(2*a + 2*b + 2*c + 2*d)
|
| 68 |
+
assert radsimp(1/(1 + r2*a + r2*b + r2*c + r2*d)) == (
|
| 69 |
+
(sqrt(2)*a + sqrt(2)*b + sqrt(2)*c + sqrt(2)*d - 1)/(2*a**2 + 4*a*b +
|
| 70 |
+
4*a*c + 4*a*d + 2*b**2 + 4*b*c + 4*b*d + 2*c**2 + 4*c*d + 2*d**2 - 1))
|
| 71 |
+
assert radsimp((y**2 - x)/(y - sqrt(x))) == \
|
| 72 |
+
sqrt(x) + y
|
| 73 |
+
assert radsimp(-(y**2 - x)/(y - sqrt(x))) == \
|
| 74 |
+
-(sqrt(x) + y)
|
| 75 |
+
assert radsimp(1/(1 - I + a*I)) == \
|
| 76 |
+
(-I*a + 1 + I)/(a**2 - 2*a + 2)
|
| 77 |
+
assert radsimp(1/((-x + y)*(x - sqrt(y)))) == \
|
| 78 |
+
(-x - sqrt(y))/((x - y)*(x**2 - y))
|
| 79 |
+
e = (3 + 3*sqrt(2))*x*(3*x - 3*sqrt(y))
|
| 80 |
+
assert radsimp(e) == x*(3 + 3*sqrt(2))*(3*x - 3*sqrt(y))
|
| 81 |
+
assert radsimp(1/e) == (
|
| 82 |
+
(-9*x + 9*sqrt(2)*x - 9*sqrt(y) + 9*sqrt(2)*sqrt(y))/(9*x*(9*x**2 -
|
| 83 |
+
9*y)))
|
| 84 |
+
assert radsimp(1 + 1/(1 + sqrt(3))) == \
|
| 85 |
+
Mul(S.Half, -1 + sqrt(3), evaluate=False) + 1
|
| 86 |
+
A = symbols("A", commutative=False)
|
| 87 |
+
assert radsimp(x**2 + sqrt(2)*x**2 - sqrt(2)*x*A) == \
|
| 88 |
+
x**2 + sqrt(2)*x**2 - sqrt(2)*x*A
|
| 89 |
+
assert radsimp(1/sqrt(5 + 2 * sqrt(6))) == -sqrt(2) + sqrt(3)
|
| 90 |
+
assert radsimp(1/sqrt(5 + 2 * sqrt(6))**3) == -(-sqrt(3) + sqrt(2))**3
|
| 91 |
+
|
| 92 |
+
# issue 6532
|
| 93 |
+
assert fraction(radsimp(1/sqrt(x))) == (sqrt(x), x)
|
| 94 |
+
assert fraction(radsimp(1/sqrt(2*x + 3))) == (sqrt(2*x + 3), 2*x + 3)
|
| 95 |
+
assert fraction(radsimp(1/sqrt(2*(x + 3)))) == (sqrt(2*x + 6), 2*x + 6)
|
| 96 |
+
|
| 97 |
+
# issue 5994
|
| 98 |
+
e = S('-(2 + 2*sqrt(2) + 4*2**(1/4))/'
|
| 99 |
+
'(1 + 2**(3/4) + 3*2**(1/4) + 3*sqrt(2))')
|
| 100 |
+
assert radsimp(e).expand() == -2*2**Rational(3, 4) - 2*2**Rational(1, 4) + 2 + 2*sqrt(2)
|
| 101 |
+
|
| 102 |
+
# issue 5986 (modifications to radimp didn't initially recognize this so
|
| 103 |
+
# the test is included here)
|
| 104 |
+
assert radsimp(1/(-sqrt(5)/2 - S.Half + (-sqrt(5)/2 - S.Half)**2)) == 1
|
| 105 |
+
|
| 106 |
+
# from issue 5934
|
| 107 |
+
eq = (
|
| 108 |
+
(-240*sqrt(2)*sqrt(sqrt(5) + 5)*sqrt(8*sqrt(5) + 40) -
|
| 109 |
+
360*sqrt(2)*sqrt(-8*sqrt(5) + 40)*sqrt(-sqrt(5) + 5) -
|
| 110 |
+
120*sqrt(10)*sqrt(-8*sqrt(5) + 40)*sqrt(-sqrt(5) + 5) +
|
| 111 |
+
120*sqrt(2)*sqrt(-sqrt(5) + 5)*sqrt(8*sqrt(5) + 40) +
|
| 112 |
+
120*sqrt(2)*sqrt(-8*sqrt(5) + 40)*sqrt(sqrt(5) + 5) +
|
| 113 |
+
120*sqrt(10)*sqrt(-sqrt(5) + 5)*sqrt(8*sqrt(5) + 40) +
|
| 114 |
+
120*sqrt(10)*sqrt(-8*sqrt(5) + 40)*sqrt(sqrt(5) + 5))/(-36000 -
|
| 115 |
+
7200*sqrt(5) + (12*sqrt(10)*sqrt(sqrt(5) + 5) +
|
| 116 |
+
24*sqrt(10)*sqrt(-sqrt(5) + 5))**2))
|
| 117 |
+
assert radsimp(eq) is S.NaN # it's 0/0
|
| 118 |
+
|
| 119 |
+
# work with normal form
|
| 120 |
+
e = 1/sqrt(sqrt(7)/7 + 2*sqrt(2) + 3*sqrt(3) + 5*sqrt(5)) + 3
|
| 121 |
+
assert radsimp(e) == (
|
| 122 |
+
-sqrt(sqrt(7) + 14*sqrt(2) + 21*sqrt(3) +
|
| 123 |
+
35*sqrt(5))*(-11654899*sqrt(35) - 1577436*sqrt(210) - 1278438*sqrt(15)
|
| 124 |
+
- 1346996*sqrt(10) + 1635060*sqrt(6) + 5709765 + 7539830*sqrt(14) +
|
| 125 |
+
8291415*sqrt(21))/1300423175 + 3)
|
| 126 |
+
|
| 127 |
+
# obey power rules
|
| 128 |
+
base = sqrt(3) - sqrt(2)
|
| 129 |
+
assert radsimp(1/base**3) == (sqrt(3) + sqrt(2))**3
|
| 130 |
+
assert radsimp(1/(-base)**3) == -(sqrt(2) + sqrt(3))**3
|
| 131 |
+
assert radsimp(1/(-base)**x) == (-base)**(-x)
|
| 132 |
+
assert radsimp(1/base**x) == (sqrt(2) + sqrt(3))**x
|
| 133 |
+
assert radsimp(root(1/(-1 - sqrt(2)), -x)) == (-1)**(-1/x)*(1 + sqrt(2))**(1/x)
|
| 134 |
+
|
| 135 |
+
# recurse
|
| 136 |
+
e = cos(1/(1 + sqrt(2)))
|
| 137 |
+
assert radsimp(e) == cos(-sqrt(2) + 1)
|
| 138 |
+
assert radsimp(e/2) == cos(-sqrt(2) + 1)/2
|
| 139 |
+
assert radsimp(1/e) == 1/cos(-sqrt(2) + 1)
|
| 140 |
+
assert radsimp(2/e) == 2/cos(-sqrt(2) + 1)
|
| 141 |
+
assert fraction(radsimp(e/sqrt(x))) == (sqrt(x)*cos(-sqrt(2)+1), x)
|
| 142 |
+
|
| 143 |
+
# test that symbolic denominators are not processed
|
| 144 |
+
r = 1 + sqrt(2)
|
| 145 |
+
assert radsimp(x/r, symbolic=False) == -x*(-sqrt(2) + 1)
|
| 146 |
+
assert radsimp(x/(y + r), symbolic=False) == x/(y + 1 + sqrt(2))
|
| 147 |
+
assert radsimp(x/(y + r)/r, symbolic=False) == \
|
| 148 |
+
-x*(-sqrt(2) + 1)/(y + 1 + sqrt(2))
|
| 149 |
+
|
| 150 |
+
# issue 7408
|
| 151 |
+
eq = sqrt(x)/sqrt(y)
|
| 152 |
+
assert radsimp(eq) == umul(sqrt(x), sqrt(y), 1/y)
|
| 153 |
+
assert radsimp(eq, symbolic=False) == eq
|
| 154 |
+
|
| 155 |
+
# issue 7498
|
| 156 |
+
assert radsimp(sqrt(x)/sqrt(y)**3) == umul(sqrt(x), sqrt(y**3), 1/y**3)
|
| 157 |
+
|
| 158 |
+
# for coverage
|
| 159 |
+
eq = sqrt(x)/y**2
|
| 160 |
+
assert radsimp(eq) == eq
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def test_radsimp_issue_3214():
|
| 164 |
+
c, p = symbols('c p', positive=True)
|
| 165 |
+
s = sqrt(c**2 - p**2)
|
| 166 |
+
b = (c + I*p - s)/(c + I*p + s)
|
| 167 |
+
assert radsimp(b) == -I*(c + I*p - sqrt(c**2 - p**2))**2/(2*c*p)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def test_collect_1():
|
| 171 |
+
"""Collect with respect to Symbol"""
|
| 172 |
+
x, y, z, n = symbols('x,y,z,n')
|
| 173 |
+
assert collect(1, x) == 1
|
| 174 |
+
assert collect( x + y*x, x ) == x * (1 + y)
|
| 175 |
+
assert collect( x + x**2, x ) == x + x**2
|
| 176 |
+
assert collect( x**2 + y*x**2, x ) == (x**2)*(1 + y)
|
| 177 |
+
assert collect( x**2 + y*x, x ) == x*y + x**2
|
| 178 |
+
assert collect( 2*x**2 + y*x**2 + 3*x*y, [x] ) == x**2*(2 + y) + 3*x*y
|
| 179 |
+
assert collect( 2*x**2 + y*x**2 + 3*x*y, [y] ) == 2*x**2 + y*(x**2 + 3*x)
|
| 180 |
+
|
| 181 |
+
assert collect( ((1 + y + x)**4).expand(), x) == ((1 + y)**4).expand() + \
|
| 182 |
+
x*(4*(1 + y)**3).expand() + x**2*(6*(1 + y)**2).expand() + \
|
| 183 |
+
x**3*(4*(1 + y)).expand() + x**4
|
| 184 |
+
# symbols can be given as any iterable
|
| 185 |
+
expr = x + y
|
| 186 |
+
assert collect(expr, expr.free_symbols) == expr
|
| 187 |
+
assert collect(x*exp(x) + sin(x)*y + sin(x)*2 + 3*x, x, exact=None
|
| 188 |
+
) == x*exp(x) + 3*x + (y + 2)*sin(x)
|
| 189 |
+
assert collect(x*exp(x) + sin(x)*y + sin(x)*2 + 3*x + y*x +
|
| 190 |
+
y*x*exp(x), x, exact=None
|
| 191 |
+
) == x*exp(x)*(y + 1) + (3 + y)*x + (y + 2)*sin(x)
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def test_collect_2():
|
| 195 |
+
"""Collect with respect to a sum"""
|
| 196 |
+
a, b, x = symbols('a,b,x')
|
| 197 |
+
assert collect(a*(cos(x) + sin(x)) + b*(cos(x) + sin(x)),
|
| 198 |
+
sin(x) + cos(x)) == (a + b)*(cos(x) + sin(x))
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def test_collect_3():
|
| 202 |
+
"""Collect with respect to a product"""
|
| 203 |
+
a, b, c = symbols('a,b,c')
|
| 204 |
+
f = Function('f')
|
| 205 |
+
x, y, z, n = symbols('x,y,z,n')
|
| 206 |
+
|
| 207 |
+
assert collect(-x/8 + x*y, -x) == x*(y - Rational(1, 8))
|
| 208 |
+
|
| 209 |
+
assert collect( 1 + x*(y**2), x*y ) == 1 + x*(y**2)
|
| 210 |
+
assert collect( x*y + a*x*y, x*y) == x*y*(1 + a)
|
| 211 |
+
assert collect( 1 + x*y + a*x*y, x*y) == 1 + x*y*(1 + a)
|
| 212 |
+
assert collect(a*x*f(x) + b*(x*f(x)), x*f(x)) == x*(a + b)*f(x)
|
| 213 |
+
|
| 214 |
+
assert collect(a*x*log(x) + b*(x*log(x)), x*log(x)) == x*(a + b)*log(x)
|
| 215 |
+
assert collect(a*x**2*log(x)**2 + b*(x*log(x))**2, x*log(x)) == \
|
| 216 |
+
x**2*log(x)**2*(a + b)
|
| 217 |
+
|
| 218 |
+
# with respect to a product of three symbols
|
| 219 |
+
assert collect(y*x*z + a*x*y*z, x*y*z) == (1 + a)*x*y*z
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def test_collect_4():
|
| 223 |
+
"""Collect with respect to a power"""
|
| 224 |
+
a, b, c, x = symbols('a,b,c,x')
|
| 225 |
+
|
| 226 |
+
assert collect(a*x**c + b*x**c, x**c) == x**c*(a + b)
|
| 227 |
+
# issue 6096: 2 stays with c (unless c is integer or x is positive0
|
| 228 |
+
assert collect(a*x**(2*c) + b*x**(2*c), x**c) == x**(2*c)*(a + b)
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
def test_collect_5():
|
| 232 |
+
"""Collect with respect to a tuple"""
|
| 233 |
+
a, x, y, z, n = symbols('a,x,y,z,n')
|
| 234 |
+
assert collect(x**2*y**4 + z*(x*y**2)**2 + z + a*z, [x*y**2, z]) in [
|
| 235 |
+
z*(1 + a + x**2*y**4) + x**2*y**4,
|
| 236 |
+
z*(1 + a) + x**2*y**4*(1 + z) ]
|
| 237 |
+
assert collect((1 + (x + y) + (x + y)**2).expand(),
|
| 238 |
+
[x, y]) == 1 + y + x*(1 + 2*y) + x**2 + y**2
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
def test_collect_pr19431():
|
| 242 |
+
"""Unevaluated collect with respect to a product"""
|
| 243 |
+
a = symbols('a')
|
| 244 |
+
assert collect(a**2*(a**2 + 1), a**2, evaluate=False)[a**2] == (a**2 + 1)
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def test_collect_D():
|
| 248 |
+
D = Derivative
|
| 249 |
+
f = Function('f')
|
| 250 |
+
x, a, b = symbols('x,a,b')
|
| 251 |
+
fx = D(f(x), x)
|
| 252 |
+
fxx = D(f(x), x, x)
|
| 253 |
+
|
| 254 |
+
assert collect(a*fx + b*fx, fx) == (a + b)*fx
|
| 255 |
+
assert collect(a*D(fx, x) + b*D(fx, x), fx) == (a + b)*D(fx, x)
|
| 256 |
+
assert collect(a*fxx + b*fxx, fx) == (a + b)*D(fx, x)
|
| 257 |
+
# issue 4784
|
| 258 |
+
assert collect(5*f(x) + 3*fx, fx) == 5*f(x) + 3*fx
|
| 259 |
+
assert collect(f(x) + f(x)*diff(f(x), x) + x*diff(f(x), x)*f(x), f(x).diff(x)) == \
|
| 260 |
+
(x*f(x) + f(x))*D(f(x), x) + f(x)
|
| 261 |
+
assert collect(f(x) + f(x)*diff(f(x), x) + x*diff(f(x), x)*f(x), f(x).diff(x), exact=True) == \
|
| 262 |
+
(x*f(x) + f(x))*D(f(x), x) + f(x)
|
| 263 |
+
assert collect(1/f(x) + 1/f(x)*diff(f(x), x) + x*diff(f(x), x)/f(x), f(x).diff(x), exact=True) == \
|
| 264 |
+
(1/f(x) + x/f(x))*D(f(x), x) + 1/f(x)
|
| 265 |
+
e = (1 + x*fx + fx)/f(x)
|
| 266 |
+
assert collect(e.expand(), fx) == fx*(x/f(x) + 1/f(x)) + 1/f(x)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
def test_collect_func():
|
| 270 |
+
f = ((x + a + 1)**3).expand()
|
| 271 |
+
|
| 272 |
+
assert collect(f, x) == a**3 + 3*a**2 + 3*a + x**3 + x**2*(3*a + 3) + \
|
| 273 |
+
x*(3*a**2 + 6*a + 3) + 1
|
| 274 |
+
assert collect(f, x, factor) == x**3 + 3*x**2*(a + 1) + 3*x*(a + 1)**2 + \
|
| 275 |
+
(a + 1)**3
|
| 276 |
+
|
| 277 |
+
assert collect(f, x, evaluate=False) == {
|
| 278 |
+
S.One: a**3 + 3*a**2 + 3*a + 1,
|
| 279 |
+
x: 3*a**2 + 6*a + 3, x**2: 3*a + 3,
|
| 280 |
+
x**3: 1
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
assert collect(f, x, factor, evaluate=False) == {
|
| 284 |
+
S.One: (a + 1)**3, x: 3*(a + 1)**2,
|
| 285 |
+
x**2: umul(S(3), a + 1), x**3: 1}
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
def test_collect_order():
|
| 289 |
+
a, b, x, t = symbols('a,b,x,t')
|
| 290 |
+
|
| 291 |
+
assert collect(t + t*x + t*x**2 + O(x**3), t) == t*(1 + x + x**2 + O(x**3))
|
| 292 |
+
assert collect(t + t*x + x**2 + O(x**3), t) == \
|
| 293 |
+
t*(1 + x + O(x**3)) + x**2 + O(x**3)
|
| 294 |
+
|
| 295 |
+
f = a*x + b*x + c*x**2 + d*x**2 + O(x**3)
|
| 296 |
+
g = x*(a + b) + x**2*(c + d) + O(x**3)
|
| 297 |
+
|
| 298 |
+
assert collect(f, x) == g
|
| 299 |
+
assert collect(f, x, distribute_order_term=False) == g
|
| 300 |
+
|
| 301 |
+
f = sin(a + b).series(b, 0, 10)
|
| 302 |
+
|
| 303 |
+
assert collect(f, [sin(a), cos(a)]) == \
|
| 304 |
+
sin(a)*cos(b).series(b, 0, 10) + cos(a)*sin(b).series(b, 0, 10)
|
| 305 |
+
assert collect(f, [sin(a), cos(a)], distribute_order_term=False) == \
|
| 306 |
+
sin(a)*cos(b).series(b, 0, 10).removeO() + \
|
| 307 |
+
cos(a)*sin(b).series(b, 0, 10).removeO() + O(b**10)
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def test_rcollect():
|
| 311 |
+
assert rcollect((x**2*y + x*y + x + y)/(x + y), y) == \
|
| 312 |
+
(x + y*(1 + x + x**2))/(x + y)
|
| 313 |
+
assert rcollect(sqrt(-((x + 1)*(y + 1))), z) == sqrt(-((x + 1)*(y + 1)))
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def test_collect_D_0():
|
| 317 |
+
D = Derivative
|
| 318 |
+
f = Function('f')
|
| 319 |
+
x, a, b = symbols('x,a,b')
|
| 320 |
+
fxx = D(f(x), x, x)
|
| 321 |
+
|
| 322 |
+
assert collect(a*fxx + b*fxx, fxx) == (a + b)*fxx
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
def test_collect_Wild():
|
| 326 |
+
"""Collect with respect to functions with Wild argument"""
|
| 327 |
+
a, b, x, y = symbols('a b x y')
|
| 328 |
+
f = Function('f')
|
| 329 |
+
w1 = Wild('.1')
|
| 330 |
+
w2 = Wild('.2')
|
| 331 |
+
assert collect(f(x) + a*f(x), f(w1)) == (1 + a)*f(x)
|
| 332 |
+
assert collect(f(x, y) + a*f(x, y), f(w1)) == f(x, y) + a*f(x, y)
|
| 333 |
+
assert collect(f(x, y) + a*f(x, y), f(w1, w2)) == (1 + a)*f(x, y)
|
| 334 |
+
assert collect(f(x, y) + a*f(x, y), f(w1, w1)) == f(x, y) + a*f(x, y)
|
| 335 |
+
assert collect(f(x, x) + a*f(x, x), f(w1, w1)) == (1 + a)*f(x, x)
|
| 336 |
+
assert collect(a*(x + 1)**y + (x + 1)**y, w1**y) == (1 + a)*(x + 1)**y
|
| 337 |
+
assert collect(a*(x + 1)**y + (x + 1)**y, w1**b) == \
|
| 338 |
+
a*(x + 1)**y + (x + 1)**y
|
| 339 |
+
assert collect(a*(x + 1)**y + (x + 1)**y, (x + 1)**w2) == \
|
| 340 |
+
(1 + a)*(x + 1)**y
|
| 341 |
+
assert collect(a*(x + 1)**y + (x + 1)**y, w1**w2) == (1 + a)*(x + 1)**y
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
def test_collect_const():
|
| 345 |
+
# coverage not provided by above tests
|
| 346 |
+
assert collect_const(2*sqrt(3) + 4*a*sqrt(5)) == \
|
| 347 |
+
2*(2*sqrt(5)*a + sqrt(3)) # let the primitive reabsorb
|
| 348 |
+
assert collect_const(2*sqrt(3) + 4*a*sqrt(5), sqrt(3)) == \
|
| 349 |
+
2*sqrt(3) + 4*a*sqrt(5)
|
| 350 |
+
assert collect_const(sqrt(2)*(1 + sqrt(2)) + sqrt(3) + x*sqrt(2)) == \
|
| 351 |
+
sqrt(2)*(x + 1 + sqrt(2)) + sqrt(3)
|
| 352 |
+
|
| 353 |
+
# issue 5290
|
| 354 |
+
assert collect_const(2*x + 2*y + 1, 2) == \
|
| 355 |
+
collect_const(2*x + 2*y + 1) == \
|
| 356 |
+
Add(S.One, Mul(2, x + y, evaluate=False), evaluate=False)
|
| 357 |
+
assert collect_const(-y - z) == Mul(-1, y + z, evaluate=False)
|
| 358 |
+
assert collect_const(2*x - 2*y - 2*z, 2) == \
|
| 359 |
+
Mul(2, x - y - z, evaluate=False)
|
| 360 |
+
assert collect_const(2*x - 2*y - 2*z, -2) == \
|
| 361 |
+
_unevaluated_Add(2*x, Mul(-2, y + z, evaluate=False))
|
| 362 |
+
|
| 363 |
+
# this is why the content_primitive is used
|
| 364 |
+
eq = (sqrt(15 + 5*sqrt(2))*x + sqrt(3 + sqrt(2))*y)*2
|
| 365 |
+
assert collect_sqrt(eq + 2) == \
|
| 366 |
+
2*sqrt(sqrt(2) + 3)*(sqrt(5)*x + y) + 2
|
| 367 |
+
|
| 368 |
+
# issue 16296
|
| 369 |
+
assert collect_const(a + b + x/2 + y/2) == a + b + Mul(S.Half, x + y, evaluate=False)
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
def test_issue_13143():
|
| 373 |
+
f = Function('f')
|
| 374 |
+
fx = f(x).diff(x)
|
| 375 |
+
e = f(x) + fx + f(x)*fx
|
| 376 |
+
# collect function before derivative
|
| 377 |
+
assert collect(e, Wild('w')) == f(x)*(fx + 1) + fx
|
| 378 |
+
e = f(x) + f(x)*fx + x*fx*f(x)
|
| 379 |
+
assert collect(e, fx) == (x*f(x) + f(x))*fx + f(x)
|
| 380 |
+
assert collect(e, f(x)) == (x*fx + fx + 1)*f(x)
|
| 381 |
+
e = f(x) + fx + f(x)*fx
|
| 382 |
+
assert collect(e, [f(x), fx]) == f(x)*(1 + fx) + fx
|
| 383 |
+
assert collect(e, [fx, f(x)]) == fx*(1 + f(x)) + f(x)
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
def test_issue_6097():
|
| 387 |
+
assert collect(a*y**(2.0*x) + b*y**(2.0*x), y**x) == (a + b)*(y**x)**2.0
|
| 388 |
+
assert collect(a*2**(2.0*x) + b*2**(2.0*x), 2**x) == (a + b)*(2**x)**2.0
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
def test_fraction_expand():
|
| 392 |
+
eq = (x + y)*y/x
|
| 393 |
+
assert eq.expand(frac=True) == fraction_expand(eq) == (x*y + y**2)/x
|
| 394 |
+
assert eq.expand() == y + y**2/x
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
def test_fraction():
|
| 398 |
+
x, y, z = map(Symbol, 'xyz')
|
| 399 |
+
A = Symbol('A', commutative=False)
|
| 400 |
+
|
| 401 |
+
assert fraction(S.Half) == (1, 2)
|
| 402 |
+
|
| 403 |
+
assert fraction(x) == (x, 1)
|
| 404 |
+
assert fraction(1/x) == (1, x)
|
| 405 |
+
assert fraction(x/y) == (x, y)
|
| 406 |
+
assert fraction(x/2) == (x, 2)
|
| 407 |
+
|
| 408 |
+
assert fraction(x*y/z) == (x*y, z)
|
| 409 |
+
assert fraction(x/(y*z)) == (x, y*z)
|
| 410 |
+
|
| 411 |
+
assert fraction(1/y**2) == (1, y**2)
|
| 412 |
+
assert fraction(x/y**2) == (x, y**2)
|
| 413 |
+
|
| 414 |
+
assert fraction((x**2 + 1)/y) == (x**2 + 1, y)
|
| 415 |
+
assert fraction(x*(y + 1)/y**7) == (x*(y + 1), y**7)
|
| 416 |
+
|
| 417 |
+
assert fraction(exp(-x), exact=True) == (exp(-x), 1)
|
| 418 |
+
assert fraction((1/(x + y))/2, exact=True) == (1, Mul(2,(x + y), evaluate=False))
|
| 419 |
+
|
| 420 |
+
assert fraction(x*A/y) == (x*A, y)
|
| 421 |
+
assert fraction(x*A**-1/y) == (x*A**-1, y)
|
| 422 |
+
|
| 423 |
+
n = symbols('n', negative=True)
|
| 424 |
+
assert fraction(exp(n)) == (1, exp(-n))
|
| 425 |
+
assert fraction(exp(-n)) == (exp(-n), 1)
|
| 426 |
+
|
| 427 |
+
p = symbols('p', positive=True)
|
| 428 |
+
assert fraction(exp(-p)*log(p), exact=True) == (exp(-p)*log(p), 1)
|
| 429 |
+
|
| 430 |
+
m = Mul(1, 1, S.Half, evaluate=False)
|
| 431 |
+
assert fraction(m) == (1, 2)
|
| 432 |
+
assert fraction(m, exact=True) == (Mul(1, 1, evaluate=False), 2)
|
| 433 |
+
|
| 434 |
+
m = Mul(1, 1, S.Half, S.Half, Pow(1, -1, evaluate=False), evaluate=False)
|
| 435 |
+
assert fraction(m) == (1, 4)
|
| 436 |
+
assert fraction(m, exact=True) == \
|
| 437 |
+
(Mul(1, 1, evaluate=False), Mul(2, 2, 1, evaluate=False))
|
| 438 |
+
|
| 439 |
+
|
| 440 |
+
def test_issue_5615():
|
| 441 |
+
aA, Re, a, b, D = symbols('aA Re a b D')
|
| 442 |
+
e = ((D**3*a + b*aA**3)/Re).expand()
|
| 443 |
+
assert collect(e, [aA**3/Re, a]) == e
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
def test_issue_5933():
|
| 447 |
+
from sympy.geometry.polygon import (Polygon, RegularPolygon)
|
| 448 |
+
from sympy.simplify.radsimp import denom
|
| 449 |
+
x = Polygon(*RegularPolygon((0, 0), 1, 5).vertices).centroid.x
|
| 450 |
+
assert abs(denom(x).n()) > 1e-12
|
| 451 |
+
assert abs(denom(radsimp(x))) > 1e-12 # in case simplify didn't handle it
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
def test_issue_14608():
|
| 455 |
+
a, b = symbols('a b', commutative=False)
|
| 456 |
+
x, y = symbols('x y')
|
| 457 |
+
raises(AttributeError, lambda: collect(a*b + b*a, a))
|
| 458 |
+
assert collect(x*y + y*(x+1), a) == x*y + y*(x+1)
|
| 459 |
+
assert collect(x*y + y*(x+1) + a*b + b*a, y) == y*(2*x + 1) + a*b + b*a
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def test_collect_abs():
|
| 463 |
+
s = abs(x) + abs(y)
|
| 464 |
+
assert collect_abs(s) == s
|
| 465 |
+
assert unchanged(Mul, abs(x), abs(y))
|
| 466 |
+
ans = Abs(x*y)
|
| 467 |
+
assert isinstance(ans, Abs)
|
| 468 |
+
assert collect_abs(abs(x)*abs(y)) == ans
|
| 469 |
+
assert collect_abs(1 + exp(abs(x)*abs(y))) == 1 + exp(ans)
|
| 470 |
+
|
| 471 |
+
# See https://github.com/sympy/sympy/issues/12910
|
| 472 |
+
p = Symbol('p', positive=True)
|
| 473 |
+
assert collect_abs(p/abs(1-p)).is_commutative is True
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
def test_issue_19149():
|
| 477 |
+
eq = exp(3*x/4)
|
| 478 |
+
assert collect(eq, exp(x)) == eq
|
| 479 |
+
|
| 480 |
+
def test_issue_19719():
|
| 481 |
+
a, b = symbols('a, b')
|
| 482 |
+
expr = a**2 * (b + 1) + (7 + 1/b)/a
|
| 483 |
+
collected = collect(expr, (a**2, 1/a), evaluate=False)
|
| 484 |
+
# Would return {_Dummy_20**(-2): b + 1, 1/a: 7 + 1/b} without xreplace
|
| 485 |
+
assert collected == {a**2: b + 1, 1/a: 7 + 1/b}
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
def test_issue_21355():
|
| 489 |
+
assert radsimp(1/(x + sqrt(x**2))) == 1/(x + sqrt(x**2))
|
| 490 |
+
assert radsimp(1/(x - sqrt(x**2))) == 1/(x - sqrt(x**2))
|
lib/python3.12/site-packages/sympy/simplify/tests/test_ratsimp.py
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.numbers import (Rational, pi)
|
| 2 |
+
from sympy.functions.elementary.exponential import log
|
| 3 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 4 |
+
from sympy.functions.special.error_functions import erf
|
| 5 |
+
from sympy.polys.domains import GF
|
| 6 |
+
from sympy.simplify.ratsimp import (ratsimp, ratsimpmodprime)
|
| 7 |
+
|
| 8 |
+
from sympy.abc import x, y, z, t, a, b, c, d, e
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_ratsimp():
|
| 12 |
+
f, g = 1/x + 1/y, (x + y)/(x*y)
|
| 13 |
+
|
| 14 |
+
assert f != g and ratsimp(f) == g
|
| 15 |
+
|
| 16 |
+
f, g = 1/(1 + 1/x), 1 - 1/(x + 1)
|
| 17 |
+
|
| 18 |
+
assert f != g and ratsimp(f) == g
|
| 19 |
+
|
| 20 |
+
f, g = x/(x + y) + y/(x + y), 1
|
| 21 |
+
|
| 22 |
+
assert f != g and ratsimp(f) == g
|
| 23 |
+
|
| 24 |
+
f, g = -x - y - y**2/(x + y) + x**2/(x + y), -2*y
|
| 25 |
+
|
| 26 |
+
assert f != g and ratsimp(f) == g
|
| 27 |
+
|
| 28 |
+
f = (a*c*x*y + a*c*z - b*d*x*y - b*d*z - b*t*x*y - b*t*x - b*t*z +
|
| 29 |
+
e*x)/(x*y + z)
|
| 30 |
+
G = [a*c - b*d - b*t + (-b*t*x + e*x)/(x*y + z),
|
| 31 |
+
a*c - b*d - b*t - ( b*t*x - e*x)/(x*y + z)]
|
| 32 |
+
|
| 33 |
+
assert f != g and ratsimp(f) in G
|
| 34 |
+
|
| 35 |
+
A = sqrt(pi)
|
| 36 |
+
|
| 37 |
+
B = log(erf(x) - 1)
|
| 38 |
+
C = log(erf(x) + 1)
|
| 39 |
+
|
| 40 |
+
D = 8 - 8*erf(x)
|
| 41 |
+
|
| 42 |
+
f = A*B/D - A*C/D + A*C*erf(x)/D - A*B*erf(x)/D + 2*A/D
|
| 43 |
+
|
| 44 |
+
assert ratsimp(f) == A*B/8 - A*C/8 - A/(4*erf(x) - 4)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def test_ratsimpmodprime():
|
| 48 |
+
a = y**5 + x + y
|
| 49 |
+
b = x - y
|
| 50 |
+
F = [x*y**5 - x - y]
|
| 51 |
+
assert ratsimpmodprime(a/b, F, x, y, order='lex') == \
|
| 52 |
+
(-x**2 - x*y - x - y) / (-x**2 + x*y)
|
| 53 |
+
|
| 54 |
+
a = x + y**2 - 2
|
| 55 |
+
b = x + y**2 - y - 1
|
| 56 |
+
F = [x*y - 1]
|
| 57 |
+
assert ratsimpmodprime(a/b, F, x, y, order='lex') == \
|
| 58 |
+
(1 + y - x)/(y - x)
|
| 59 |
+
|
| 60 |
+
a = 5*x**3 + 21*x**2 + 4*x*y + 23*x + 12*y + 15
|
| 61 |
+
b = 7*x**3 - y*x**2 + 31*x**2 + 2*x*y + 15*y + 37*x + 21
|
| 62 |
+
F = [x**2 + y**2 - 1]
|
| 63 |
+
assert ratsimpmodprime(a/b, F, x, y, order='lex') == \
|
| 64 |
+
(1 + 5*y - 5*x)/(8*y - 6*x)
|
| 65 |
+
|
| 66 |
+
a = x*y - x - 2*y + 4
|
| 67 |
+
b = x + y**2 - 2*y
|
| 68 |
+
F = [x - 2, y - 3]
|
| 69 |
+
assert ratsimpmodprime(a/b, F, x, y, order='lex') == \
|
| 70 |
+
Rational(2, 5)
|
| 71 |
+
|
| 72 |
+
# Test a bug where denominators would be dropped
|
| 73 |
+
assert ratsimpmodprime(x, [y - 2*x], order='lex') == \
|
| 74 |
+
y/2
|
| 75 |
+
|
| 76 |
+
a = (x**5 + 2*x**4 + 2*x**3 + 2*x**2 + x + 2/x + x**(-2))
|
| 77 |
+
assert ratsimpmodprime(a, [x + 1], domain=GF(2)) == 1
|
| 78 |
+
assert ratsimpmodprime(a, [x + 1], domain=GF(3)) == -1
|
lib/python3.12/site-packages/sympy/simplify/tests/test_rewrite.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.numbers import I
|
| 2 |
+
from sympy.core.symbol import symbols
|
| 3 |
+
from sympy.functions.elementary.exponential import exp
|
| 4 |
+
from sympy.functions.elementary.trigonometric import (cos, cot, sin)
|
| 5 |
+
from sympy.testing.pytest import _both_exp_pow
|
| 6 |
+
|
| 7 |
+
x, y, z, n = symbols('x,y,z,n')
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@_both_exp_pow
|
| 11 |
+
def test_has():
|
| 12 |
+
assert cot(x).has(x)
|
| 13 |
+
assert cot(x).has(cot)
|
| 14 |
+
assert not cot(x).has(sin)
|
| 15 |
+
assert sin(x).has(x)
|
| 16 |
+
assert sin(x).has(sin)
|
| 17 |
+
assert not sin(x).has(cot)
|
| 18 |
+
assert exp(x).has(exp)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@_both_exp_pow
|
| 22 |
+
def test_sin_exp_rewrite():
|
| 23 |
+
assert sin(x).rewrite(sin, exp) == -I/2*(exp(I*x) - exp(-I*x))
|
| 24 |
+
assert sin(x).rewrite(sin, exp).rewrite(exp, sin) == sin(x)
|
| 25 |
+
assert cos(x).rewrite(cos, exp).rewrite(exp, cos) == cos(x)
|
| 26 |
+
assert (sin(5*y) - sin(
|
| 27 |
+
2*x)).rewrite(sin, exp).rewrite(exp, sin) == sin(5*y) - sin(2*x)
|
| 28 |
+
assert sin(x + y).rewrite(sin, exp).rewrite(exp, sin) == sin(x + y)
|
| 29 |
+
assert cos(x + y).rewrite(cos, exp).rewrite(exp, cos) == cos(x + y)
|
| 30 |
+
# This next test currently passes... not clear whether it should or not?
|
| 31 |
+
assert cos(x).rewrite(cos, exp).rewrite(exp, sin) == cos(x)
|
lib/python3.12/site-packages/sympy/simplify/tests/test_simplify.py
ADDED
|
@@ -0,0 +1,1087 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.concrete.summations import Sum
|
| 2 |
+
from sympy.core.add import Add
|
| 3 |
+
from sympy.core.basic import Basic
|
| 4 |
+
from sympy.core.expr import unchanged
|
| 5 |
+
from sympy.core.function import (count_ops, diff, expand, expand_multinomial, Function, Derivative)
|
| 6 |
+
from sympy.core.mul import Mul, _keep_coeff
|
| 7 |
+
from sympy.core import GoldenRatio
|
| 8 |
+
from sympy.core.numbers import (E, Float, I, oo, pi, Rational, zoo)
|
| 9 |
+
from sympy.core.relational import (Eq, Lt, Gt, Ge, Le)
|
| 10 |
+
from sympy.core.singleton import S
|
| 11 |
+
from sympy.core.symbol import (Symbol, symbols)
|
| 12 |
+
from sympy.core.sympify import sympify
|
| 13 |
+
from sympy.functions.combinatorial.factorials import (binomial, factorial)
|
| 14 |
+
from sympy.functions.elementary.complexes import (Abs, sign)
|
| 15 |
+
from sympy.functions.elementary.exponential import (exp, exp_polar, log)
|
| 16 |
+
from sympy.functions.elementary.hyperbolic import (cosh, csch, sinh)
|
| 17 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 18 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 19 |
+
from sympy.functions.elementary.trigonometric import (acos, asin, atan, cos, sin, sinc, tan)
|
| 20 |
+
from sympy.functions.special.error_functions import erf
|
| 21 |
+
from sympy.functions.special.gamma_functions import gamma
|
| 22 |
+
from sympy.functions.special.hyper import hyper
|
| 23 |
+
from sympy.functions.special.tensor_functions import KroneckerDelta
|
| 24 |
+
from sympy.geometry.polygon import rad
|
| 25 |
+
from sympy.integrals.integrals import (Integral, integrate)
|
| 26 |
+
from sympy.logic.boolalg import (And, Or)
|
| 27 |
+
from sympy.matrices.dense import (Matrix, eye)
|
| 28 |
+
from sympy.matrices.expressions.matexpr import MatrixSymbol
|
| 29 |
+
from sympy.polys.polytools import (factor, Poly)
|
| 30 |
+
from sympy.simplify.simplify import (besselsimp, hypersimp, inversecombine, logcombine, nsimplify, nthroot, posify, separatevars, signsimp, simplify)
|
| 31 |
+
from sympy.solvers.solvers import solve
|
| 32 |
+
|
| 33 |
+
from sympy.testing.pytest import XFAIL, slow, _both_exp_pow
|
| 34 |
+
from sympy.abc import x, y, z, t, a, b, c, d, e, f, g, h, i, n
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def test_issue_7263():
|
| 38 |
+
assert abs((simplify(30.8**2 - 82.5**2 * sin(rad(11.6))**2)).evalf() - \
|
| 39 |
+
673.447451402970) < 1e-12
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def test_factorial_simplify():
|
| 43 |
+
# There are more tests in test_factorials.py.
|
| 44 |
+
x = Symbol('x')
|
| 45 |
+
assert simplify(factorial(x)/x) == gamma(x)
|
| 46 |
+
assert simplify(factorial(factorial(x))) == factorial(factorial(x))
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def test_simplify_expr():
|
| 50 |
+
x, y, z, k, n, m, w, s, A = symbols('x,y,z,k,n,m,w,s,A')
|
| 51 |
+
f = Function('f')
|
| 52 |
+
|
| 53 |
+
assert all(simplify(tmp) == tmp for tmp in [I, E, oo, x, -x, -oo, -E, -I])
|
| 54 |
+
|
| 55 |
+
e = 1/x + 1/y
|
| 56 |
+
assert e != (x + y)/(x*y)
|
| 57 |
+
assert simplify(e) == (x + y)/(x*y)
|
| 58 |
+
|
| 59 |
+
e = A**2*s**4/(4*pi*k*m**3)
|
| 60 |
+
assert simplify(e) == e
|
| 61 |
+
|
| 62 |
+
e = (4 + 4*x - 2*(2 + 2*x))/(2 + 2*x)
|
| 63 |
+
assert simplify(e) == 0
|
| 64 |
+
|
| 65 |
+
e = (-4*x*y**2 - 2*y**3 - 2*x**2*y)/(x + y)**2
|
| 66 |
+
assert simplify(e) == -2*y
|
| 67 |
+
|
| 68 |
+
e = -x - y - (x + y)**(-1)*y**2 + (x + y)**(-1)*x**2
|
| 69 |
+
assert simplify(e) == -2*y
|
| 70 |
+
|
| 71 |
+
e = (x + x*y)/x
|
| 72 |
+
assert simplify(e) == 1 + y
|
| 73 |
+
|
| 74 |
+
e = (f(x) + y*f(x))/f(x)
|
| 75 |
+
assert simplify(e) == 1 + y
|
| 76 |
+
|
| 77 |
+
e = (2 * (1/n - cos(n * pi)/n))/pi
|
| 78 |
+
assert simplify(e) == (-cos(pi*n) + 1)/(pi*n)*2
|
| 79 |
+
|
| 80 |
+
e = integrate(1/(x**3 + 1), x).diff(x)
|
| 81 |
+
assert simplify(e) == 1/(x**3 + 1)
|
| 82 |
+
|
| 83 |
+
e = integrate(x/(x**2 + 3*x + 1), x).diff(x)
|
| 84 |
+
assert simplify(e) == x/(x**2 + 3*x + 1)
|
| 85 |
+
|
| 86 |
+
f = Symbol('f')
|
| 87 |
+
A = Matrix([[2*k - m*w**2, -k], [-k, k - m*w**2]]).inv()
|
| 88 |
+
assert simplify((A*Matrix([0, f]))[1] -
|
| 89 |
+
(-f*(2*k - m*w**2)/(k**2 - (k - m*w**2)*(2*k - m*w**2)))) == 0
|
| 90 |
+
|
| 91 |
+
f = -x + y/(z + t) + z*x/(z + t) + z*a/(z + t) + t*x/(z + t)
|
| 92 |
+
assert simplify(f) == (y + a*z)/(z + t)
|
| 93 |
+
|
| 94 |
+
# issue 10347
|
| 95 |
+
expr = -x*(y**2 - 1)*(2*y**2*(x**2 - 1)/(a*(x**2 - y**2)**2) + (x**2 - 1)
|
| 96 |
+
/(a*(x**2 - y**2)))/(a*(x**2 - y**2)) + x*(-2*x**2*sqrt(-x**2*y**2 + x**2
|
| 97 |
+
+ y**2 - 1)*sin(z)/(a*(x**2 - y**2)**2) - x**2*sqrt(-x**2*y**2 + x**2 +
|
| 98 |
+
y**2 - 1)*sin(z)/(a*(x**2 - 1)*(x**2 - y**2)) + (x**2*sqrt((-x**2 + 1)*
|
| 99 |
+
(y**2 - 1))*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*sin(z)/(x**2 - 1) + sqrt(
|
| 100 |
+
(-x**2 + 1)*(y**2 - 1))*(x*(-x*y**2 + x)/sqrt(-x**2*y**2 + x**2 + y**2 -
|
| 101 |
+
1) + sqrt(-x**2*y**2 + x**2 + y**2 - 1))*sin(z))/(a*sqrt((-x**2 + 1)*(
|
| 102 |
+
y**2 - 1))*(x**2 - y**2)))*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*sin(z)/(a*
|
| 103 |
+
(x**2 - y**2)) + x*(-2*x**2*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*cos(z)/(a*
|
| 104 |
+
(x**2 - y**2)**2) - x**2*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*cos(z)/(a*
|
| 105 |
+
(x**2 - 1)*(x**2 - y**2)) + (x**2*sqrt((-x**2 + 1)*(y**2 - 1))*sqrt(-x**2
|
| 106 |
+
*y**2 + x**2 + y**2 - 1)*cos(z)/(x**2 - 1) + x*sqrt((-x**2 + 1)*(y**2 -
|
| 107 |
+
1))*(-x*y**2 + x)*cos(z)/sqrt(-x**2*y**2 + x**2 + y**2 - 1) + sqrt((-x**2
|
| 108 |
+
+ 1)*(y**2 - 1))*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*cos(z))/(a*sqrt((-x**2
|
| 109 |
+
+ 1)*(y**2 - 1))*(x**2 - y**2)))*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*cos(
|
| 110 |
+
z)/(a*(x**2 - y**2)) - y*sqrt((-x**2 + 1)*(y**2 - 1))*(-x*y*sqrt(-x**2*
|
| 111 |
+
y**2 + x**2 + y**2 - 1)*sin(z)/(a*(x**2 - y**2)*(y**2 - 1)) + 2*x*y*sqrt(
|
| 112 |
+
-x**2*y**2 + x**2 + y**2 - 1)*sin(z)/(a*(x**2 - y**2)**2) + (x*y*sqrt((
|
| 113 |
+
-x**2 + 1)*(y**2 - 1))*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*sin(z)/(y**2 -
|
| 114 |
+
1) + x*sqrt((-x**2 + 1)*(y**2 - 1))*(-x**2*y + y)*sin(z)/sqrt(-x**2*y**2
|
| 115 |
+
+ x**2 + y**2 - 1))/(a*sqrt((-x**2 + 1)*(y**2 - 1))*(x**2 - y**2)))*sin(
|
| 116 |
+
z)/(a*(x**2 - y**2)) + y*(x**2 - 1)*(-2*x*y*(x**2 - 1)/(a*(x**2 - y**2)
|
| 117 |
+
**2) + 2*x*y/(a*(x**2 - y**2)))/(a*(x**2 - y**2)) + y*(x**2 - 1)*(y**2 -
|
| 118 |
+
1)*(-x*y*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*cos(z)/(a*(x**2 - y**2)*(y**2
|
| 119 |
+
- 1)) + 2*x*y*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*cos(z)/(a*(x**2 - y**2)
|
| 120 |
+
**2) + (x*y*sqrt((-x**2 + 1)*(y**2 - 1))*sqrt(-x**2*y**2 + x**2 + y**2 -
|
| 121 |
+
1)*cos(z)/(y**2 - 1) + x*sqrt((-x**2 + 1)*(y**2 - 1))*(-x**2*y + y)*cos(
|
| 122 |
+
z)/sqrt(-x**2*y**2 + x**2 + y**2 - 1))/(a*sqrt((-x**2 + 1)*(y**2 - 1)
|
| 123 |
+
)*(x**2 - y**2)))*cos(z)/(a*sqrt((-x**2 + 1)*(y**2 - 1))*(x**2 - y**2)
|
| 124 |
+
) - x*sqrt((-x**2 + 1)*(y**2 - 1))*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*sin(
|
| 125 |
+
z)**2/(a**2*(x**2 - 1)*(x**2 - y**2)*(y**2 - 1)) - x*sqrt((-x**2 + 1)*(
|
| 126 |
+
y**2 - 1))*sqrt(-x**2*y**2 + x**2 + y**2 - 1)*cos(z)**2/(a**2*(x**2 - 1)*(
|
| 127 |
+
x**2 - y**2)*(y**2 - 1))
|
| 128 |
+
assert simplify(expr) == 2*x/(a**2*(x**2 - y**2))
|
| 129 |
+
|
| 130 |
+
#issue 17631
|
| 131 |
+
assert simplify('((-1/2)*Boole(True)*Boole(False)-1)*Boole(True)') == \
|
| 132 |
+
Mul(sympify('(2 + Boole(True)*Boole(False))'), sympify('-Boole(True)/2'))
|
| 133 |
+
|
| 134 |
+
A, B = symbols('A,B', commutative=False)
|
| 135 |
+
|
| 136 |
+
assert simplify(A*B - B*A) == A*B - B*A
|
| 137 |
+
assert simplify(A/(1 + y/x)) == x*A/(x + y)
|
| 138 |
+
assert simplify(A*(1/x + 1/y)) == A/x + A/y #(x + y)*A/(x*y)
|
| 139 |
+
|
| 140 |
+
assert simplify(log(2) + log(3)) == log(6)
|
| 141 |
+
assert simplify(log(2*x) - log(2)) == log(x)
|
| 142 |
+
|
| 143 |
+
assert simplify(hyper([], [], x)) == exp(x)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
def test_issue_3557():
|
| 147 |
+
f_1 = x*a + y*b + z*c - 1
|
| 148 |
+
f_2 = x*d + y*e + z*f - 1
|
| 149 |
+
f_3 = x*g + y*h + z*i - 1
|
| 150 |
+
|
| 151 |
+
solutions = solve([f_1, f_2, f_3], x, y, z, simplify=False)
|
| 152 |
+
|
| 153 |
+
assert simplify(solutions[y]) == \
|
| 154 |
+
(a*i + c*d + f*g - a*f - c*g - d*i)/ \
|
| 155 |
+
(a*e*i + b*f*g + c*d*h - a*f*h - b*d*i - c*e*g)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def test_simplify_other():
|
| 159 |
+
assert simplify(sin(x)**2 + cos(x)**2) == 1
|
| 160 |
+
assert simplify(gamma(x + 1)/gamma(x)) == x
|
| 161 |
+
assert simplify(sin(x)**2 + cos(x)**2 + factorial(x)/gamma(x)) == 1 + x
|
| 162 |
+
assert simplify(
|
| 163 |
+
Eq(sin(x)**2 + cos(x)**2, factorial(x)/gamma(x))) == Eq(x, 1)
|
| 164 |
+
nc = symbols('nc', commutative=False)
|
| 165 |
+
assert simplify(x + x*nc) == x*(1 + nc)
|
| 166 |
+
# issue 6123
|
| 167 |
+
# f = exp(-I*(k*sqrt(t) + x/(2*sqrt(t)))**2)
|
| 168 |
+
# ans = integrate(f, (k, -oo, oo), conds='none')
|
| 169 |
+
ans = I*(-pi*x*exp(I*pi*Rational(-3, 4) + I*x**2/(4*t))*erf(x*exp(I*pi*Rational(-3, 4))/
|
| 170 |
+
(2*sqrt(t)))/(2*sqrt(t)) + pi*x*exp(I*pi*Rational(-3, 4) + I*x**2/(4*t))/
|
| 171 |
+
(2*sqrt(t)))*exp(-I*x**2/(4*t))/(sqrt(pi)*x) - I*sqrt(pi) * \
|
| 172 |
+
(-erf(x*exp(I*pi/4)/(2*sqrt(t))) + 1)*exp(I*pi/4)/(2*sqrt(t))
|
| 173 |
+
assert simplify(ans) == -(-1)**Rational(3, 4)*sqrt(pi)/sqrt(t)
|
| 174 |
+
# issue 6370
|
| 175 |
+
assert simplify(2**(2 + x)/4) == 2**x
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
@_both_exp_pow
|
| 179 |
+
def test_simplify_complex():
|
| 180 |
+
cosAsExp = cos(x)._eval_rewrite_as_exp(x)
|
| 181 |
+
tanAsExp = tan(x)._eval_rewrite_as_exp(x)
|
| 182 |
+
assert simplify(cosAsExp*tanAsExp) == sin(x) # issue 4341
|
| 183 |
+
|
| 184 |
+
# issue 10124
|
| 185 |
+
assert simplify(exp(Matrix([[0, -1], [1, 0]]))) == Matrix([[cos(1),
|
| 186 |
+
-sin(1)], [sin(1), cos(1)]])
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def test_simplify_ratio():
|
| 190 |
+
# roots of x**3-3*x+5
|
| 191 |
+
roots = ['(1/2 - sqrt(3)*I/2)*(sqrt(21)/2 + 5/2)**(1/3) + 1/((1/2 - '
|
| 192 |
+
'sqrt(3)*I/2)*(sqrt(21)/2 + 5/2)**(1/3))',
|
| 193 |
+
'1/((1/2 + sqrt(3)*I/2)*(sqrt(21)/2 + 5/2)**(1/3)) + '
|
| 194 |
+
'(1/2 + sqrt(3)*I/2)*(sqrt(21)/2 + 5/2)**(1/3)',
|
| 195 |
+
'-(sqrt(21)/2 + 5/2)**(1/3) - 1/(sqrt(21)/2 + 5/2)**(1/3)']
|
| 196 |
+
|
| 197 |
+
for r in roots:
|
| 198 |
+
r = S(r)
|
| 199 |
+
assert count_ops(simplify(r, ratio=1)) <= count_ops(r)
|
| 200 |
+
# If ratio=oo, simplify() is always applied:
|
| 201 |
+
assert simplify(r, ratio=oo) is not r
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def test_simplify_measure():
|
| 205 |
+
measure1 = lambda expr: len(str(expr))
|
| 206 |
+
measure2 = lambda expr: -count_ops(expr)
|
| 207 |
+
# Return the most complicated result
|
| 208 |
+
expr = (x + 1)/(x + sin(x)**2 + cos(x)**2)
|
| 209 |
+
assert measure1(simplify(expr, measure=measure1)) <= measure1(expr)
|
| 210 |
+
assert measure2(simplify(expr, measure=measure2)) <= measure2(expr)
|
| 211 |
+
|
| 212 |
+
expr2 = Eq(sin(x)**2 + cos(x)**2, 1)
|
| 213 |
+
assert measure1(simplify(expr2, measure=measure1)) <= measure1(expr2)
|
| 214 |
+
assert measure2(simplify(expr2, measure=measure2)) <= measure2(expr2)
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
def test_simplify_rational():
|
| 218 |
+
expr = 2**x*2.**y
|
| 219 |
+
assert simplify(expr, rational = True) == 2**(x+y)
|
| 220 |
+
assert simplify(expr, rational = None) == 2.0**(x+y)
|
| 221 |
+
assert simplify(expr, rational = False) == expr
|
| 222 |
+
assert simplify('0.9 - 0.8 - 0.1', rational = True) == 0
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def test_simplify_issue_1308():
|
| 226 |
+
assert simplify(exp(Rational(-1, 2)) + exp(Rational(-3, 2))) == \
|
| 227 |
+
(1 + E)*exp(Rational(-3, 2))
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def test_issue_5652():
|
| 231 |
+
assert simplify(E + exp(-E)) == exp(-E) + E
|
| 232 |
+
n = symbols('n', commutative=False)
|
| 233 |
+
assert simplify(n + n**(-n)) == n + n**(-n)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
def test_simplify_fail1():
|
| 237 |
+
x = Symbol('x')
|
| 238 |
+
y = Symbol('y')
|
| 239 |
+
e = (x + y)**2/(-4*x*y**2 - 2*y**3 - 2*x**2*y)
|
| 240 |
+
assert simplify(e) == 1 / (-2*y)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def test_nthroot():
|
| 244 |
+
assert nthroot(90 + 34*sqrt(7), 3) == sqrt(7) + 3
|
| 245 |
+
q = 1 + sqrt(2) - 2*sqrt(3) + sqrt(6) + sqrt(7)
|
| 246 |
+
assert nthroot(expand_multinomial(q**3), 3) == q
|
| 247 |
+
assert nthroot(41 + 29*sqrt(2), 5) == 1 + sqrt(2)
|
| 248 |
+
assert nthroot(-41 - 29*sqrt(2), 5) == -1 - sqrt(2)
|
| 249 |
+
expr = 1320*sqrt(10) + 4216 + 2576*sqrt(6) + 1640*sqrt(15)
|
| 250 |
+
assert nthroot(expr, 5) == 1 + sqrt(6) + sqrt(15)
|
| 251 |
+
q = 1 + sqrt(2) + sqrt(3) + sqrt(5)
|
| 252 |
+
assert expand_multinomial(nthroot(expand_multinomial(q**5), 5)) == q
|
| 253 |
+
q = 1 + sqrt(2) + 7*sqrt(6) + 2*sqrt(10)
|
| 254 |
+
assert nthroot(expand_multinomial(q**5), 5, 8) == q
|
| 255 |
+
q = 1 + sqrt(2) - 2*sqrt(3) + 1171*sqrt(6)
|
| 256 |
+
assert nthroot(expand_multinomial(q**3), 3) == q
|
| 257 |
+
assert nthroot(expand_multinomial(q**6), 6) == q
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
def test_nthroot1():
|
| 261 |
+
q = 1 + sqrt(2) + sqrt(3) + S.One/10**20
|
| 262 |
+
p = expand_multinomial(q**5)
|
| 263 |
+
assert nthroot(p, 5) == q
|
| 264 |
+
q = 1 + sqrt(2) + sqrt(3) + S.One/10**30
|
| 265 |
+
p = expand_multinomial(q**5)
|
| 266 |
+
assert nthroot(p, 5) == q
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
@_both_exp_pow
|
| 270 |
+
def test_separatevars():
|
| 271 |
+
x, y, z, n = symbols('x,y,z,n')
|
| 272 |
+
assert separatevars(2*n*x*z + 2*x*y*z) == 2*x*z*(n + y)
|
| 273 |
+
assert separatevars(x*z + x*y*z) == x*z*(1 + y)
|
| 274 |
+
assert separatevars(pi*x*z + pi*x*y*z) == pi*x*z*(1 + y)
|
| 275 |
+
assert separatevars(x*y**2*sin(x) + x*sin(x)*sin(y)) == \
|
| 276 |
+
x*(sin(y) + y**2)*sin(x)
|
| 277 |
+
assert separatevars(x*exp(x + y) + x*exp(x)) == x*(1 + exp(y))*exp(x)
|
| 278 |
+
assert separatevars((x*(y + 1))**z).is_Pow # != x**z*(1 + y)**z
|
| 279 |
+
assert separatevars(1 + x + y + x*y) == (x + 1)*(y + 1)
|
| 280 |
+
assert separatevars(y/pi*exp(-(z - x)/cos(n))) == \
|
| 281 |
+
y*exp(x/cos(n))*exp(-z/cos(n))/pi
|
| 282 |
+
assert separatevars((x + y)*(x - y) + y**2 + 2*x + 1) == (x + 1)**2
|
| 283 |
+
# issue 4858
|
| 284 |
+
p = Symbol('p', positive=True)
|
| 285 |
+
assert separatevars(sqrt(p**2 + x*p**2)) == p*sqrt(1 + x)
|
| 286 |
+
assert separatevars(sqrt(y*(p**2 + x*p**2))) == p*sqrt(y*(1 + x))
|
| 287 |
+
assert separatevars(sqrt(y*(p**2 + x*p**2)), force=True) == \
|
| 288 |
+
p*sqrt(y)*sqrt(1 + x)
|
| 289 |
+
# issue 4865
|
| 290 |
+
assert separatevars(sqrt(x*y)).is_Pow
|
| 291 |
+
assert separatevars(sqrt(x*y), force=True) == sqrt(x)*sqrt(y)
|
| 292 |
+
# issue 4957
|
| 293 |
+
# any type sequence for symbols is fine
|
| 294 |
+
assert separatevars(((2*x + 2)*y), dict=True, symbols=()) == \
|
| 295 |
+
{'coeff': 1, x: 2*x + 2, y: y}
|
| 296 |
+
# separable
|
| 297 |
+
assert separatevars(((2*x + 2)*y), dict=True, symbols=[x]) == \
|
| 298 |
+
{'coeff': y, x: 2*x + 2}
|
| 299 |
+
assert separatevars(((2*x + 2)*y), dict=True, symbols=[]) == \
|
| 300 |
+
{'coeff': 1, x: 2*x + 2, y: y}
|
| 301 |
+
assert separatevars(((2*x + 2)*y), dict=True) == \
|
| 302 |
+
{'coeff': 1, x: 2*x + 2, y: y}
|
| 303 |
+
assert separatevars(((2*x + 2)*y), dict=True, symbols=None) == \
|
| 304 |
+
{'coeff': y*(2*x + 2)}
|
| 305 |
+
# not separable
|
| 306 |
+
assert separatevars(3, dict=True) is None
|
| 307 |
+
assert separatevars(2*x + y, dict=True, symbols=()) is None
|
| 308 |
+
assert separatevars(2*x + y, dict=True) is None
|
| 309 |
+
assert separatevars(2*x + y, dict=True, symbols=None) == {'coeff': 2*x + y}
|
| 310 |
+
# issue 4808
|
| 311 |
+
n, m = symbols('n,m', commutative=False)
|
| 312 |
+
assert separatevars(m + n*m) == (1 + n)*m
|
| 313 |
+
assert separatevars(x + x*n) == x*(1 + n)
|
| 314 |
+
# issue 4910
|
| 315 |
+
f = Function('f')
|
| 316 |
+
assert separatevars(f(x) + x*f(x)) == f(x) + x*f(x)
|
| 317 |
+
# a noncommutable object present
|
| 318 |
+
eq = x*(1 + hyper((), (), y*z))
|
| 319 |
+
assert separatevars(eq) == eq
|
| 320 |
+
|
| 321 |
+
s = separatevars(abs(x*y))
|
| 322 |
+
assert s == abs(x)*abs(y) and s.is_Mul
|
| 323 |
+
z = cos(1)**2 + sin(1)**2 - 1
|
| 324 |
+
a = abs(x*z)
|
| 325 |
+
s = separatevars(a)
|
| 326 |
+
assert not a.is_Mul and s.is_Mul and s == abs(x)*abs(z)
|
| 327 |
+
s = separatevars(abs(x*y*z))
|
| 328 |
+
assert s == abs(x)*abs(y)*abs(z)
|
| 329 |
+
|
| 330 |
+
# abs(x+y)/abs(z) would be better but we test this here to
|
| 331 |
+
# see that it doesn't raise
|
| 332 |
+
assert separatevars(abs((x+y)/z)) == abs((x+y)/z)
|
| 333 |
+
|
| 334 |
+
|
| 335 |
+
def test_separatevars_advanced_factor():
|
| 336 |
+
x, y, z = symbols('x,y,z')
|
| 337 |
+
assert separatevars(1 + log(x)*log(y) + log(x) + log(y)) == \
|
| 338 |
+
(log(x) + 1)*(log(y) + 1)
|
| 339 |
+
assert separatevars(1 + x - log(z) - x*log(z) - exp(y)*log(z) -
|
| 340 |
+
x*exp(y)*log(z) + x*exp(y) + exp(y)) == \
|
| 341 |
+
-((x + 1)*(log(z) - 1)*(exp(y) + 1))
|
| 342 |
+
x, y = symbols('x,y', positive=True)
|
| 343 |
+
assert separatevars(1 + log(x**log(y)) + log(x*y)) == \
|
| 344 |
+
(log(x) + 1)*(log(y) + 1)
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def test_hypersimp():
|
| 348 |
+
n, k = symbols('n,k', integer=True)
|
| 349 |
+
|
| 350 |
+
assert hypersimp(factorial(k), k) == k + 1
|
| 351 |
+
assert hypersimp(factorial(k**2), k) is None
|
| 352 |
+
|
| 353 |
+
assert hypersimp(1/factorial(k), k) == 1/(k + 1)
|
| 354 |
+
|
| 355 |
+
assert hypersimp(2**k/factorial(k)**2, k) == 2/(k + 1)**2
|
| 356 |
+
|
| 357 |
+
assert hypersimp(binomial(n, k), k) == (n - k)/(k + 1)
|
| 358 |
+
assert hypersimp(binomial(n + 1, k), k) == (n - k + 1)/(k + 1)
|
| 359 |
+
|
| 360 |
+
term = (4*k + 1)*factorial(k)/factorial(2*k + 1)
|
| 361 |
+
assert hypersimp(term, k) == S.Half*((4*k + 5)/(3 + 14*k + 8*k**2))
|
| 362 |
+
|
| 363 |
+
term = 1/((2*k - 1)*factorial(2*k + 1))
|
| 364 |
+
assert hypersimp(term, k) == (k - S.Half)/((k + 1)*(2*k + 1)*(2*k + 3))
|
| 365 |
+
|
| 366 |
+
term = binomial(n, k)*(-1)**k/factorial(k)
|
| 367 |
+
assert hypersimp(term, k) == (k - n)/(k + 1)**2
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
def test_nsimplify():
|
| 371 |
+
x = Symbol("x")
|
| 372 |
+
assert nsimplify(0) == 0
|
| 373 |
+
assert nsimplify(-1) == -1
|
| 374 |
+
assert nsimplify(1) == 1
|
| 375 |
+
assert nsimplify(1 + x) == 1 + x
|
| 376 |
+
assert nsimplify(2.7) == Rational(27, 10)
|
| 377 |
+
assert nsimplify(1 - GoldenRatio) == (1 - sqrt(5))/2
|
| 378 |
+
assert nsimplify((1 + sqrt(5))/4, [GoldenRatio]) == GoldenRatio/2
|
| 379 |
+
assert nsimplify(2/GoldenRatio, [GoldenRatio]) == 2*GoldenRatio - 2
|
| 380 |
+
assert nsimplify(exp(pi*I*Rational(5, 3), evaluate=False)) == \
|
| 381 |
+
sympify('1/2 - sqrt(3)*I/2')
|
| 382 |
+
assert nsimplify(sin(pi*Rational(3, 5), evaluate=False)) == \
|
| 383 |
+
sympify('sqrt(sqrt(5)/8 + 5/8)')
|
| 384 |
+
assert nsimplify(sqrt(atan('1', evaluate=False))*(2 + I), [pi]) == \
|
| 385 |
+
sqrt(pi) + sqrt(pi)/2*I
|
| 386 |
+
assert nsimplify(2 + exp(2*atan('1/4')*I)) == sympify('49/17 + 8*I/17')
|
| 387 |
+
assert nsimplify(pi, tolerance=0.01) == Rational(22, 7)
|
| 388 |
+
assert nsimplify(pi, tolerance=0.001) == Rational(355, 113)
|
| 389 |
+
assert nsimplify(0.33333, tolerance=1e-4) == Rational(1, 3)
|
| 390 |
+
assert nsimplify(2.0**(1/3.), tolerance=0.001) == Rational(635, 504)
|
| 391 |
+
assert nsimplify(2.0**(1/3.), tolerance=0.001, full=True) == \
|
| 392 |
+
2**Rational(1, 3)
|
| 393 |
+
assert nsimplify(x + .5, rational=True) == S.Half + x
|
| 394 |
+
assert nsimplify(1/.3 + x, rational=True) == Rational(10, 3) + x
|
| 395 |
+
assert nsimplify(log(3).n(), rational=True) == \
|
| 396 |
+
sympify('109861228866811/100000000000000')
|
| 397 |
+
assert nsimplify(Float(0.272198261287950), [pi, log(2)]) == pi*log(2)/8
|
| 398 |
+
assert nsimplify(Float(0.272198261287950).n(3), [pi, log(2)]) == \
|
| 399 |
+
-pi/4 - log(2) + Rational(7, 4)
|
| 400 |
+
assert nsimplify(x/7.0) == x/7
|
| 401 |
+
assert nsimplify(pi/1e2) == pi/100
|
| 402 |
+
assert nsimplify(pi/1e2, rational=False) == pi/100.0
|
| 403 |
+
assert nsimplify(pi/1e-7) == 10000000*pi
|
| 404 |
+
assert not nsimplify(
|
| 405 |
+
factor(-3.0*z**2*(z**2)**(-2.5) + 3*(z**2)**(-1.5))).atoms(Float)
|
| 406 |
+
e = x**0.0
|
| 407 |
+
assert e.is_Pow and nsimplify(x**0.0) == 1
|
| 408 |
+
assert nsimplify(3.333333, tolerance=0.1, rational=True) == Rational(10, 3)
|
| 409 |
+
assert nsimplify(3.333333, tolerance=0.01, rational=True) == Rational(10, 3)
|
| 410 |
+
assert nsimplify(3.666666, tolerance=0.1, rational=True) == Rational(11, 3)
|
| 411 |
+
assert nsimplify(3.666666, tolerance=0.01, rational=True) == Rational(11, 3)
|
| 412 |
+
assert nsimplify(33, tolerance=10, rational=True) == Rational(33)
|
| 413 |
+
assert nsimplify(33.33, tolerance=10, rational=True) == Rational(30)
|
| 414 |
+
assert nsimplify(37.76, tolerance=10, rational=True) == Rational(40)
|
| 415 |
+
assert nsimplify(-203.1) == Rational(-2031, 10)
|
| 416 |
+
assert nsimplify(.2, tolerance=0) == Rational(1, 5)
|
| 417 |
+
assert nsimplify(-.2, tolerance=0) == Rational(-1, 5)
|
| 418 |
+
assert nsimplify(.2222, tolerance=0) == Rational(1111, 5000)
|
| 419 |
+
assert nsimplify(-.2222, tolerance=0) == Rational(-1111, 5000)
|
| 420 |
+
# issue 7211, PR 4112
|
| 421 |
+
assert nsimplify(S(2e-8)) == Rational(1, 50000000)
|
| 422 |
+
# issue 7322 direct test
|
| 423 |
+
assert nsimplify(1e-42, rational=True) != 0
|
| 424 |
+
# issue 10336
|
| 425 |
+
inf = Float('inf')
|
| 426 |
+
infs = (-oo, oo, inf, -inf)
|
| 427 |
+
for zi in infs:
|
| 428 |
+
ans = sign(zi)*oo
|
| 429 |
+
assert nsimplify(zi) == ans
|
| 430 |
+
assert nsimplify(zi + x) == x + ans
|
| 431 |
+
|
| 432 |
+
assert nsimplify(0.33333333, rational=True, rational_conversion='exact') == Rational(0.33333333)
|
| 433 |
+
|
| 434 |
+
# Make sure nsimplify on expressions uses full precision
|
| 435 |
+
assert nsimplify(pi.evalf(100)*x, rational_conversion='exact').evalf(100) == pi.evalf(100)*x
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
def test_issue_9448():
|
| 439 |
+
tmp = sympify("1/(1 - (-1)**(2/3) - (-1)**(1/3)) + 1/(1 + (-1)**(2/3) + (-1)**(1/3))")
|
| 440 |
+
assert nsimplify(tmp) == S.Half
|
| 441 |
+
|
| 442 |
+
|
| 443 |
+
def test_extract_minus_sign():
|
| 444 |
+
x = Symbol("x")
|
| 445 |
+
y = Symbol("y")
|
| 446 |
+
a = Symbol("a")
|
| 447 |
+
b = Symbol("b")
|
| 448 |
+
assert simplify(-x/-y) == x/y
|
| 449 |
+
assert simplify(-x/y) == -x/y
|
| 450 |
+
assert simplify(x/y) == x/y
|
| 451 |
+
assert simplify(x/-y) == -x/y
|
| 452 |
+
assert simplify(-x/0) == zoo*x
|
| 453 |
+
assert simplify(Rational(-5, 0)) is zoo
|
| 454 |
+
assert simplify(-a*x/(-y - b)) == a*x/(b + y)
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
def test_diff():
|
| 458 |
+
x = Symbol("x")
|
| 459 |
+
y = Symbol("y")
|
| 460 |
+
f = Function("f")
|
| 461 |
+
g = Function("g")
|
| 462 |
+
assert simplify(g(x).diff(x)*f(x).diff(x) - f(x).diff(x)*g(x).diff(x)) == 0
|
| 463 |
+
assert simplify(2*f(x)*f(x).diff(x) - diff(f(x)**2, x)) == 0
|
| 464 |
+
assert simplify(diff(1/f(x), x) + f(x).diff(x)/f(x)**2) == 0
|
| 465 |
+
assert simplify(f(x).diff(x, y) - f(x).diff(y, x)) == 0
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
def test_logcombine_1():
|
| 469 |
+
x, y = symbols("x,y")
|
| 470 |
+
a = Symbol("a")
|
| 471 |
+
z, w = symbols("z,w", positive=True)
|
| 472 |
+
b = Symbol("b", real=True)
|
| 473 |
+
assert logcombine(log(x) + 2*log(y)) == log(x) + 2*log(y)
|
| 474 |
+
assert logcombine(log(x) + 2*log(y), force=True) == log(x*y**2)
|
| 475 |
+
assert logcombine(a*log(w) + log(z)) == a*log(w) + log(z)
|
| 476 |
+
assert logcombine(b*log(z) + b*log(x)) == log(z**b) + b*log(x)
|
| 477 |
+
assert logcombine(b*log(z) - log(w)) == log(z**b/w)
|
| 478 |
+
assert logcombine(log(x)*log(z)) == log(x)*log(z)
|
| 479 |
+
assert logcombine(log(w)*log(x)) == log(w)*log(x)
|
| 480 |
+
assert logcombine(cos(-2*log(z) + b*log(w))) in [cos(log(w**b/z**2)),
|
| 481 |
+
cos(log(z**2/w**b))]
|
| 482 |
+
assert logcombine(log(log(x) - log(y)) - log(z), force=True) == \
|
| 483 |
+
log(log(x/y)/z)
|
| 484 |
+
assert logcombine((2 + I)*log(x), force=True) == (2 + I)*log(x)
|
| 485 |
+
assert logcombine((x**2 + log(x) - log(y))/(x*y), force=True) == \
|
| 486 |
+
(x**2 + log(x/y))/(x*y)
|
| 487 |
+
# the following could also give log(z*x**log(y**2)), what we
|
| 488 |
+
# are testing is that a canonical result is obtained
|
| 489 |
+
assert logcombine(log(x)*2*log(y) + log(z), force=True) == \
|
| 490 |
+
log(z*y**log(x**2))
|
| 491 |
+
assert logcombine((x*y + sqrt(x**4 + y**4) + log(x) - log(y))/(pi*x**Rational(2, 3)*
|
| 492 |
+
sqrt(y)**3), force=True) == (
|
| 493 |
+
x*y + sqrt(x**4 + y**4) + log(x/y))/(pi*x**Rational(2, 3)*y**Rational(3, 2))
|
| 494 |
+
assert logcombine(gamma(-log(x/y))*acos(-log(x/y)), force=True) == \
|
| 495 |
+
acos(-log(x/y))*gamma(-log(x/y))
|
| 496 |
+
|
| 497 |
+
assert logcombine(2*log(z)*log(w)*log(x) + log(z) + log(w)) == \
|
| 498 |
+
log(z**log(w**2))*log(x) + log(w*z)
|
| 499 |
+
assert logcombine(3*log(w) + 3*log(z)) == log(w**3*z**3)
|
| 500 |
+
assert logcombine(x*(y + 1) + log(2) + log(3)) == x*(y + 1) + log(6)
|
| 501 |
+
assert logcombine((x + y)*log(w) + (-x - y)*log(3)) == (x + y)*log(w/3)
|
| 502 |
+
# a single unknown can combine
|
| 503 |
+
assert logcombine(log(x) + log(2)) == log(2*x)
|
| 504 |
+
eq = log(abs(x)) + log(abs(y))
|
| 505 |
+
assert logcombine(eq) == eq
|
| 506 |
+
reps = {x: 0, y: 0}
|
| 507 |
+
assert log(abs(x)*abs(y)).subs(reps) != eq.subs(reps)
|
| 508 |
+
|
| 509 |
+
|
| 510 |
+
def test_logcombine_complex_coeff():
|
| 511 |
+
i = Integral((sin(x**2) + cos(x**3))/x, x)
|
| 512 |
+
assert logcombine(i, force=True) == i
|
| 513 |
+
assert logcombine(i + 2*log(x), force=True) == \
|
| 514 |
+
i + log(x**2)
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
def test_issue_5950():
|
| 518 |
+
x, y = symbols("x,y", positive=True)
|
| 519 |
+
assert logcombine(log(3) - log(2)) == log(Rational(3,2), evaluate=False)
|
| 520 |
+
assert logcombine(log(x) - log(y)) == log(x/y)
|
| 521 |
+
assert logcombine(log(Rational(3,2), evaluate=False) - log(2)) == \
|
| 522 |
+
log(Rational(3,4), evaluate=False)
|
| 523 |
+
|
| 524 |
+
|
| 525 |
+
def test_posify():
|
| 526 |
+
x = symbols('x')
|
| 527 |
+
|
| 528 |
+
assert str(posify(
|
| 529 |
+
x +
|
| 530 |
+
Symbol('p', positive=True) +
|
| 531 |
+
Symbol('n', negative=True))) == '(_x + n + p, {_x: x})'
|
| 532 |
+
|
| 533 |
+
eq, rep = posify(1/x)
|
| 534 |
+
assert log(eq).expand().subs(rep) == -log(x)
|
| 535 |
+
assert str(posify([x, 1 + x])) == '([_x, _x + 1], {_x: x})'
|
| 536 |
+
|
| 537 |
+
p = symbols('p', positive=True)
|
| 538 |
+
n = symbols('n', negative=True)
|
| 539 |
+
orig = [x, n, p]
|
| 540 |
+
modified, reps = posify(orig)
|
| 541 |
+
assert str(modified) == '[_x, n, p]'
|
| 542 |
+
assert [w.subs(reps) for w in modified] == orig
|
| 543 |
+
|
| 544 |
+
assert str(Integral(posify(1/x + y)[0], (y, 1, 3)).expand()) == \
|
| 545 |
+
'Integral(1/_x, (y, 1, 3)) + Integral(_y, (y, 1, 3))'
|
| 546 |
+
assert str(Sum(posify(1/x**n)[0], (n,1,3)).expand()) == \
|
| 547 |
+
'Sum(_x**(-n), (n, 1, 3))'
|
| 548 |
+
|
| 549 |
+
# issue 16438
|
| 550 |
+
k = Symbol('k', finite=True)
|
| 551 |
+
eq, rep = posify(k)
|
| 552 |
+
assert eq.assumptions0 == {'positive': True, 'zero': False, 'imaginary': False,
|
| 553 |
+
'nonpositive': False, 'commutative': True, 'hermitian': True, 'real': True, 'nonzero': True,
|
| 554 |
+
'nonnegative': True, 'negative': False, 'complex': True, 'finite': True,
|
| 555 |
+
'infinite': False, 'extended_real':True, 'extended_negative': False,
|
| 556 |
+
'extended_nonnegative': True, 'extended_nonpositive': False,
|
| 557 |
+
'extended_nonzero': True, 'extended_positive': True}
|
| 558 |
+
|
| 559 |
+
|
| 560 |
+
def test_issue_4194():
|
| 561 |
+
# simplify should call cancel
|
| 562 |
+
f = Function('f')
|
| 563 |
+
assert simplify((4*x + 6*f(y))/(2*x + 3*f(y))) == 2
|
| 564 |
+
|
| 565 |
+
|
| 566 |
+
@XFAIL
|
| 567 |
+
def test_simplify_float_vs_integer():
|
| 568 |
+
# Test for issue 4473:
|
| 569 |
+
# https://github.com/sympy/sympy/issues/4473
|
| 570 |
+
assert simplify(x**2.0 - x**2) == 0
|
| 571 |
+
assert simplify(x**2 - x**2.0) == 0
|
| 572 |
+
|
| 573 |
+
|
| 574 |
+
def test_as_content_primitive():
|
| 575 |
+
assert (x/2 + y).as_content_primitive() == (S.Half, x + 2*y)
|
| 576 |
+
assert (x/2 + y).as_content_primitive(clear=False) == (S.One, x/2 + y)
|
| 577 |
+
assert (y*(x/2 + y)).as_content_primitive() == (S.Half, y*(x + 2*y))
|
| 578 |
+
assert (y*(x/2 + y)).as_content_primitive(clear=False) == (S.One, y*(x/2 + y))
|
| 579 |
+
|
| 580 |
+
# although the _as_content_primitive methods do not alter the underlying structure,
|
| 581 |
+
# the as_content_primitive function will touch up the expression and join
|
| 582 |
+
# bases that would otherwise have not been joined.
|
| 583 |
+
assert (x*(2 + 2*x)*(3*x + 3)**2).as_content_primitive() == \
|
| 584 |
+
(18, x*(x + 1)**3)
|
| 585 |
+
assert (2 + 2*x + 2*y*(3 + 3*y)).as_content_primitive() == \
|
| 586 |
+
(2, x + 3*y*(y + 1) + 1)
|
| 587 |
+
assert ((2 + 6*x)**2).as_content_primitive() == \
|
| 588 |
+
(4, (3*x + 1)**2)
|
| 589 |
+
assert ((2 + 6*x)**(2*y)).as_content_primitive() == \
|
| 590 |
+
(1, (_keep_coeff(S(2), (3*x + 1)))**(2*y))
|
| 591 |
+
assert (5 + 10*x + 2*y*(3 + 3*y)).as_content_primitive() == \
|
| 592 |
+
(1, 10*x + 6*y*(y + 1) + 5)
|
| 593 |
+
assert (5*(x*(1 + y)) + 2*x*(3 + 3*y)).as_content_primitive() == \
|
| 594 |
+
(11, x*(y + 1))
|
| 595 |
+
assert ((5*(x*(1 + y)) + 2*x*(3 + 3*y))**2).as_content_primitive() == \
|
| 596 |
+
(121, x**2*(y + 1)**2)
|
| 597 |
+
assert (y**2).as_content_primitive() == \
|
| 598 |
+
(1, y**2)
|
| 599 |
+
assert (S.Infinity).as_content_primitive() == (1, oo)
|
| 600 |
+
eq = x**(2 + y)
|
| 601 |
+
assert (eq).as_content_primitive() == (1, eq)
|
| 602 |
+
assert (S.Half**(2 + x)).as_content_primitive() == (Rational(1, 4), 2**-x)
|
| 603 |
+
assert (Rational(-1, 2)**(2 + x)).as_content_primitive() == \
|
| 604 |
+
(Rational(1, 4), (Rational(-1, 2))**x)
|
| 605 |
+
assert (Rational(-1, 2)**(2 + x)).as_content_primitive() == \
|
| 606 |
+
(Rational(1, 4), Rational(-1, 2)**x)
|
| 607 |
+
assert (4**((1 + y)/2)).as_content_primitive() == (2, 4**(y/2))
|
| 608 |
+
assert (3**((1 + y)/2)).as_content_primitive() == \
|
| 609 |
+
(1, 3**(Mul(S.Half, 1 + y, evaluate=False)))
|
| 610 |
+
assert (5**Rational(3, 4)).as_content_primitive() == (1, 5**Rational(3, 4))
|
| 611 |
+
assert (5**Rational(7, 4)).as_content_primitive() == (5, 5**Rational(3, 4))
|
| 612 |
+
assert Add(z*Rational(5, 7), 0.5*x, y*Rational(3, 2), evaluate=False).as_content_primitive() == \
|
| 613 |
+
(Rational(1, 14), 7.0*x + 21*y + 10*z)
|
| 614 |
+
assert (2**Rational(3, 4) + 2**Rational(1, 4)*sqrt(3)).as_content_primitive(radical=True) == \
|
| 615 |
+
(1, 2**Rational(1, 4)*(sqrt(2) + sqrt(3)))
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
def test_signsimp():
|
| 619 |
+
e = x*(-x + 1) + x*(x - 1)
|
| 620 |
+
assert signsimp(Eq(e, 0)) is S.true
|
| 621 |
+
assert Abs(x - 1) == Abs(1 - x)
|
| 622 |
+
assert signsimp(y - x) == y - x
|
| 623 |
+
assert signsimp(y - x, evaluate=False) == Mul(-1, x - y, evaluate=False)
|
| 624 |
+
|
| 625 |
+
|
| 626 |
+
def test_besselsimp():
|
| 627 |
+
from sympy.functions.special.bessel import (besseli, besselj, bessely)
|
| 628 |
+
from sympy.integrals.transforms import cosine_transform
|
| 629 |
+
assert besselsimp(exp(-I*pi*y/2)*besseli(y, z*exp_polar(I*pi/2))) == \
|
| 630 |
+
besselj(y, z)
|
| 631 |
+
assert besselsimp(exp(-I*pi*a/2)*besseli(a, 2*sqrt(x)*exp_polar(I*pi/2))) == \
|
| 632 |
+
besselj(a, 2*sqrt(x))
|
| 633 |
+
assert besselsimp(sqrt(2)*sqrt(pi)*x**Rational(1, 4)*exp(I*pi/4)*exp(-I*pi*a/2) *
|
| 634 |
+
besseli(Rational(-1, 2), sqrt(x)*exp_polar(I*pi/2)) *
|
| 635 |
+
besseli(a, sqrt(x)*exp_polar(I*pi/2))/2) == \
|
| 636 |
+
besselj(a, sqrt(x)) * cos(sqrt(x))
|
| 637 |
+
assert besselsimp(besseli(Rational(-1, 2), z)) == \
|
| 638 |
+
sqrt(2)*cosh(z)/(sqrt(pi)*sqrt(z))
|
| 639 |
+
assert besselsimp(besseli(a, z*exp_polar(-I*pi/2))) == \
|
| 640 |
+
exp(-I*pi*a/2)*besselj(a, z)
|
| 641 |
+
assert cosine_transform(1/t*sin(a/t), t, y) == \
|
| 642 |
+
sqrt(2)*sqrt(pi)*besselj(0, 2*sqrt(a)*sqrt(y))/2
|
| 643 |
+
|
| 644 |
+
assert besselsimp(x**2*(a*(-2*besselj(5*I, x) + besselj(-2 + 5*I, x) +
|
| 645 |
+
besselj(2 + 5*I, x)) + b*(-2*bessely(5*I, x) + bessely(-2 + 5*I, x) +
|
| 646 |
+
bessely(2 + 5*I, x)))/4 + x*(a*(besselj(-1 + 5*I, x)/2 - besselj(1 + 5*I, x)/2)
|
| 647 |
+
+ b*(bessely(-1 + 5*I, x)/2 - bessely(1 + 5*I, x)/2)) + (x**2 + 25)*(a*besselj(5*I, x)
|
| 648 |
+
+ b*bessely(5*I, x))) == 0
|
| 649 |
+
|
| 650 |
+
assert besselsimp(81*x**2*(a*(besselj(Rational(-5, 3), 9*x) - 2*besselj(Rational(1, 3), 9*x) + besselj(Rational(7, 3), 9*x))
|
| 651 |
+
+ b*(bessely(Rational(-5, 3), 9*x) - 2*bessely(Rational(1, 3), 9*x) + bessely(Rational(7, 3), 9*x)))/4 + x*(a*(9*besselj(Rational(-2, 3), 9*x)/2
|
| 652 |
+
- 9*besselj(Rational(4, 3), 9*x)/2) + b*(9*bessely(Rational(-2, 3), 9*x)/2 - 9*bessely(Rational(4, 3), 9*x)/2)) +
|
| 653 |
+
(81*x**2 - Rational(1, 9))*(a*besselj(Rational(1, 3), 9*x) + b*bessely(Rational(1, 3), 9*x))) == 0
|
| 654 |
+
|
| 655 |
+
assert besselsimp(besselj(a-1,x) + besselj(a+1, x) - 2*a*besselj(a, x)/x) == 0
|
| 656 |
+
|
| 657 |
+
assert besselsimp(besselj(a-1,x) + besselj(a+1, x) + besselj(a, x)) == (2*a + x)*besselj(a, x)/x
|
| 658 |
+
|
| 659 |
+
assert besselsimp(x**2* besselj(a,x) + x**3*besselj(a+1, x) + besselj(a+2, x)) == \
|
| 660 |
+
2*a*x*besselj(a + 1, x) + x**3*besselj(a + 1, x) - x**2*besselj(a + 2, x) + 2*x*besselj(a + 1, x) + besselj(a + 2, x)
|
| 661 |
+
|
| 662 |
+
def test_Piecewise():
|
| 663 |
+
e1 = x*(x + y) - y*(x + y)
|
| 664 |
+
e2 = sin(x)**2 + cos(x)**2
|
| 665 |
+
e3 = expand((x + y)*y/x)
|
| 666 |
+
s1 = simplify(e1)
|
| 667 |
+
s2 = simplify(e2)
|
| 668 |
+
s3 = simplify(e3)
|
| 669 |
+
assert simplify(Piecewise((e1, x < e2), (e3, True))) == \
|
| 670 |
+
Piecewise((s1, x < s2), (s3, True))
|
| 671 |
+
|
| 672 |
+
|
| 673 |
+
def test_polymorphism():
|
| 674 |
+
class A(Basic):
|
| 675 |
+
def _eval_simplify(x, **kwargs):
|
| 676 |
+
return S.One
|
| 677 |
+
|
| 678 |
+
a = A(S(5), S(2))
|
| 679 |
+
assert simplify(a) == 1
|
| 680 |
+
|
| 681 |
+
|
| 682 |
+
def test_issue_from_PR1599():
|
| 683 |
+
n1, n2, n3, n4 = symbols('n1 n2 n3 n4', negative=True)
|
| 684 |
+
assert simplify(I*sqrt(n1)) == -sqrt(-n1)
|
| 685 |
+
|
| 686 |
+
|
| 687 |
+
def test_issue_6811():
|
| 688 |
+
eq = (x + 2*y)*(2*x + 2)
|
| 689 |
+
assert simplify(eq) == (x + 1)*(x + 2*y)*2
|
| 690 |
+
# reject the 2-arg Mul -- these are a headache for test writing
|
| 691 |
+
assert simplify(eq.expand()) == \
|
| 692 |
+
2*x**2 + 4*x*y + 2*x + 4*y
|
| 693 |
+
|
| 694 |
+
|
| 695 |
+
def test_issue_6920():
|
| 696 |
+
e = [cos(x) + I*sin(x), cos(x) - I*sin(x),
|
| 697 |
+
cosh(x) - sinh(x), cosh(x) + sinh(x)]
|
| 698 |
+
ok = [exp(I*x), exp(-I*x), exp(-x), exp(x)]
|
| 699 |
+
# wrap in f to show that the change happens wherever ei occurs
|
| 700 |
+
f = Function('f')
|
| 701 |
+
assert [simplify(f(ei)).args[0] for ei in e] == ok
|
| 702 |
+
|
| 703 |
+
|
| 704 |
+
def test_issue_7001():
|
| 705 |
+
from sympy.abc import r, R
|
| 706 |
+
assert simplify(-(r*Piecewise((pi*Rational(4, 3), r <= R),
|
| 707 |
+
(-8*pi*R**3/(3*r**3), True)) + 2*Piecewise((pi*r*Rational(4, 3), r <= R),
|
| 708 |
+
(4*pi*R**3/(3*r**2), True)))/(4*pi*r)) == \
|
| 709 |
+
Piecewise((-1, r <= R), (0, True))
|
| 710 |
+
|
| 711 |
+
|
| 712 |
+
def test_inequality_no_auto_simplify():
|
| 713 |
+
# no simplify on creation but can be simplified
|
| 714 |
+
lhs = cos(x)**2 + sin(x)**2
|
| 715 |
+
rhs = 2
|
| 716 |
+
e = Lt(lhs, rhs, evaluate=False)
|
| 717 |
+
assert e is not S.true
|
| 718 |
+
assert simplify(e)
|
| 719 |
+
|
| 720 |
+
|
| 721 |
+
def test_issue_9398():
|
| 722 |
+
from sympy.core.numbers import Number
|
| 723 |
+
from sympy.polys.polytools import cancel
|
| 724 |
+
assert cancel(1e-14) != 0
|
| 725 |
+
assert cancel(1e-14*I) != 0
|
| 726 |
+
|
| 727 |
+
assert simplify(1e-14) != 0
|
| 728 |
+
assert simplify(1e-14*I) != 0
|
| 729 |
+
|
| 730 |
+
assert (I*Number(1.)*Number(10)**Number(-14)).simplify() != 0
|
| 731 |
+
|
| 732 |
+
assert cancel(1e-20) != 0
|
| 733 |
+
assert cancel(1e-20*I) != 0
|
| 734 |
+
|
| 735 |
+
assert simplify(1e-20) != 0
|
| 736 |
+
assert simplify(1e-20*I) != 0
|
| 737 |
+
|
| 738 |
+
assert cancel(1e-100) != 0
|
| 739 |
+
assert cancel(1e-100*I) != 0
|
| 740 |
+
|
| 741 |
+
assert simplify(1e-100) != 0
|
| 742 |
+
assert simplify(1e-100*I) != 0
|
| 743 |
+
|
| 744 |
+
f = Float("1e-1000")
|
| 745 |
+
assert cancel(f) != 0
|
| 746 |
+
assert cancel(f*I) != 0
|
| 747 |
+
|
| 748 |
+
assert simplify(f) != 0
|
| 749 |
+
assert simplify(f*I) != 0
|
| 750 |
+
|
| 751 |
+
|
| 752 |
+
def test_issue_9324_simplify():
|
| 753 |
+
M = MatrixSymbol('M', 10, 10)
|
| 754 |
+
e = M[0, 0] + M[5, 4] + 1304
|
| 755 |
+
assert simplify(e) == e
|
| 756 |
+
|
| 757 |
+
|
| 758 |
+
def test_issue_9817_simplify():
|
| 759 |
+
# simplify on trace of substituted explicit quadratic form of matrix
|
| 760 |
+
# expressions (a scalar) should return without errors (AttributeError)
|
| 761 |
+
# See issue #9817 and #9190 for the original bug more discussion on this
|
| 762 |
+
from sympy.matrices.expressions import Identity, trace
|
| 763 |
+
v = MatrixSymbol('v', 3, 1)
|
| 764 |
+
A = MatrixSymbol('A', 3, 3)
|
| 765 |
+
x = Matrix([i + 1 for i in range(3)])
|
| 766 |
+
X = Identity(3)
|
| 767 |
+
quadratic = v.T * A * v
|
| 768 |
+
assert simplify((trace(quadratic.as_explicit())).xreplace({v:x, A:X})) == 14
|
| 769 |
+
|
| 770 |
+
|
| 771 |
+
def test_issue_13474():
|
| 772 |
+
x = Symbol('x')
|
| 773 |
+
assert simplify(x + csch(sinc(1))) == x + csch(sinc(1))
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
@_both_exp_pow
|
| 777 |
+
def test_simplify_function_inverse():
|
| 778 |
+
# "inverse" attribute does not guarantee that f(g(x)) is x
|
| 779 |
+
# so this simplification should not happen automatically.
|
| 780 |
+
# See issue #12140
|
| 781 |
+
x, y = symbols('x, y')
|
| 782 |
+
g = Function('g')
|
| 783 |
+
|
| 784 |
+
class f(Function):
|
| 785 |
+
def inverse(self, argindex=1):
|
| 786 |
+
return g
|
| 787 |
+
|
| 788 |
+
assert simplify(f(g(x))) == f(g(x))
|
| 789 |
+
assert inversecombine(f(g(x))) == x
|
| 790 |
+
assert simplify(f(g(x)), inverse=True) == x
|
| 791 |
+
assert simplify(f(g(sin(x)**2 + cos(x)**2)), inverse=True) == 1
|
| 792 |
+
assert simplify(f(g(x, y)), inverse=True) == f(g(x, y))
|
| 793 |
+
assert unchanged(asin, sin(x))
|
| 794 |
+
assert simplify(asin(sin(x))) == asin(sin(x))
|
| 795 |
+
assert simplify(2*asin(sin(3*x)), inverse=True) == 6*x
|
| 796 |
+
assert simplify(log(exp(x))) == log(exp(x))
|
| 797 |
+
assert simplify(log(exp(x)), inverse=True) == x
|
| 798 |
+
assert simplify(exp(log(x)), inverse=True) == x
|
| 799 |
+
assert simplify(log(exp(x), 2), inverse=True) == x/log(2)
|
| 800 |
+
assert simplify(log(exp(x), 2, evaluate=False), inverse=True) == x/log(2)
|
| 801 |
+
|
| 802 |
+
|
| 803 |
+
def test_clear_coefficients():
|
| 804 |
+
from sympy.simplify.simplify import clear_coefficients
|
| 805 |
+
assert clear_coefficients(4*y*(6*x + 3)) == (y*(2*x + 1), 0)
|
| 806 |
+
assert clear_coefficients(4*y*(6*x + 3) - 2) == (y*(2*x + 1), Rational(1, 6))
|
| 807 |
+
assert clear_coefficients(4*y*(6*x + 3) - 2, x) == (y*(2*x + 1), x/12 + Rational(1, 6))
|
| 808 |
+
assert clear_coefficients(sqrt(2) - 2) == (sqrt(2), 2)
|
| 809 |
+
assert clear_coefficients(4*sqrt(2) - 2) == (sqrt(2), S.Half)
|
| 810 |
+
assert clear_coefficients(S(3), x) == (0, x - 3)
|
| 811 |
+
assert clear_coefficients(S.Infinity, x) == (S.Infinity, x)
|
| 812 |
+
assert clear_coefficients(-S.Pi, x) == (S.Pi, -x)
|
| 813 |
+
assert clear_coefficients(2 - S.Pi/3, x) == (pi, -3*x + 6)
|
| 814 |
+
|
| 815 |
+
def test_nc_simplify():
|
| 816 |
+
from sympy.simplify.simplify import nc_simplify
|
| 817 |
+
from sympy.matrices.expressions import MatPow, Identity
|
| 818 |
+
from sympy.core import Pow
|
| 819 |
+
from functools import reduce
|
| 820 |
+
|
| 821 |
+
a, b, c, d = symbols('a b c d', commutative = False)
|
| 822 |
+
x = Symbol('x')
|
| 823 |
+
A = MatrixSymbol("A", x, x)
|
| 824 |
+
B = MatrixSymbol("B", x, x)
|
| 825 |
+
C = MatrixSymbol("C", x, x)
|
| 826 |
+
D = MatrixSymbol("D", x, x)
|
| 827 |
+
subst = {a: A, b: B, c: C, d:D}
|
| 828 |
+
funcs = {Add: lambda x,y: x+y, Mul: lambda x,y: x*y }
|
| 829 |
+
|
| 830 |
+
def _to_matrix(expr):
|
| 831 |
+
if expr in subst:
|
| 832 |
+
return subst[expr]
|
| 833 |
+
if isinstance(expr, Pow):
|
| 834 |
+
return MatPow(_to_matrix(expr.args[0]), expr.args[1])
|
| 835 |
+
elif isinstance(expr, (Add, Mul)):
|
| 836 |
+
return reduce(funcs[expr.func],[_to_matrix(a) for a in expr.args])
|
| 837 |
+
else:
|
| 838 |
+
return expr*Identity(x)
|
| 839 |
+
|
| 840 |
+
def _check(expr, simplified, deep=True, matrix=True):
|
| 841 |
+
assert nc_simplify(expr, deep=deep) == simplified
|
| 842 |
+
assert expand(expr) == expand(simplified)
|
| 843 |
+
if matrix:
|
| 844 |
+
m_simp = _to_matrix(simplified).doit(inv_expand=False)
|
| 845 |
+
assert nc_simplify(_to_matrix(expr), deep=deep) == m_simp
|
| 846 |
+
|
| 847 |
+
_check(a*b*a*b*a*b*c*(a*b)**3*c, ((a*b)**3*c)**2)
|
| 848 |
+
_check(a*b*(a*b)**-2*a*b, 1)
|
| 849 |
+
_check(a**2*b*a*b*a*b*(a*b)**-1, a*(a*b)**2, matrix=False)
|
| 850 |
+
_check(b*a*b**2*a*b**2*a*b**2, b*(a*b**2)**3)
|
| 851 |
+
_check(a*b*a**2*b*a**2*b*a**3, (a*b*a)**3*a**2)
|
| 852 |
+
_check(a**2*b*a**4*b*a**4*b*a**2, (a**2*b*a**2)**3)
|
| 853 |
+
_check(a**3*b*a**4*b*a**4*b*a, a**3*(b*a**4)**3*a**-3)
|
| 854 |
+
_check(a*b*a*b + a*b*c*x*a*b*c, (a*b)**2 + x*(a*b*c)**2)
|
| 855 |
+
_check(a*b*a*b*c*a*b*a*b*c, ((a*b)**2*c)**2)
|
| 856 |
+
_check(b**-1*a**-1*(a*b)**2, a*b)
|
| 857 |
+
_check(a**-1*b*c**-1, (c*b**-1*a)**-1)
|
| 858 |
+
expr = a**3*b*a**4*b*a**4*b*a**2*b*a**2*(b*a**2)**2*b*a**2*b*a**2
|
| 859 |
+
for _ in range(10):
|
| 860 |
+
expr *= a*b
|
| 861 |
+
_check(expr, a**3*(b*a**4)**2*(b*a**2)**6*(a*b)**10)
|
| 862 |
+
_check((a*b*a*b)**2, (a*b*a*b)**2, deep=False)
|
| 863 |
+
_check(a*b*(c*d)**2, a*b*(c*d)**2)
|
| 864 |
+
expr = b**-1*(a**-1*b**-1 - a**-1*c*b**-1)**-1*a**-1
|
| 865 |
+
assert nc_simplify(expr) == (1-c)**-1
|
| 866 |
+
# commutative expressions should be returned without an error
|
| 867 |
+
assert nc_simplify(2*x**2) == 2*x**2
|
| 868 |
+
|
| 869 |
+
def test_issue_15965():
|
| 870 |
+
A = Sum(z*x**y, (x, 1, a))
|
| 871 |
+
anew = z*Sum(x**y, (x, 1, a))
|
| 872 |
+
B = Integral(x*y, x)
|
| 873 |
+
bdo = x**2*y/2
|
| 874 |
+
assert simplify(A + B) == anew + bdo
|
| 875 |
+
assert simplify(A) == anew
|
| 876 |
+
assert simplify(B) == bdo
|
| 877 |
+
assert simplify(B, doit=False) == y*Integral(x, x)
|
| 878 |
+
|
| 879 |
+
|
| 880 |
+
def test_issue_17137():
|
| 881 |
+
assert simplify(cos(x)**I) == cos(x)**I
|
| 882 |
+
assert simplify(cos(x)**(2 + 3*I)) == cos(x)**(2 + 3*I)
|
| 883 |
+
|
| 884 |
+
|
| 885 |
+
def test_issue_21869():
|
| 886 |
+
x = Symbol('x', real=True)
|
| 887 |
+
y = Symbol('y', real=True)
|
| 888 |
+
expr = And(Eq(x**2, 4), Le(x, y))
|
| 889 |
+
assert expr.simplify() == expr
|
| 890 |
+
|
| 891 |
+
expr = And(Eq(x**2, 4), Eq(x, 2))
|
| 892 |
+
assert expr.simplify() == Eq(x, 2)
|
| 893 |
+
|
| 894 |
+
expr = And(Eq(x**3, x**2), Eq(x, 1))
|
| 895 |
+
assert expr.simplify() == Eq(x, 1)
|
| 896 |
+
|
| 897 |
+
expr = And(Eq(sin(x), x**2), Eq(x, 0))
|
| 898 |
+
assert expr.simplify() == Eq(x, 0)
|
| 899 |
+
|
| 900 |
+
expr = And(Eq(x**3, x**2), Eq(x, 2))
|
| 901 |
+
assert expr.simplify() == S.false
|
| 902 |
+
|
| 903 |
+
expr = And(Eq(y, x**2), Eq(x, 1))
|
| 904 |
+
assert expr.simplify() == And(Eq(y,1), Eq(x, 1))
|
| 905 |
+
|
| 906 |
+
expr = And(Eq(y**2, 1), Eq(y, x**2), Eq(x, 1))
|
| 907 |
+
assert expr.simplify() == And(Eq(y,1), Eq(x, 1))
|
| 908 |
+
|
| 909 |
+
expr = And(Eq(y**2, 4), Eq(y, 2*x**2), Eq(x, 1))
|
| 910 |
+
assert expr.simplify() == And(Eq(y,2), Eq(x, 1))
|
| 911 |
+
|
| 912 |
+
expr = And(Eq(y**2, 4), Eq(y, x**2), Eq(x, 1))
|
| 913 |
+
assert expr.simplify() == S.false
|
| 914 |
+
|
| 915 |
+
|
| 916 |
+
def test_issue_7971_21740():
|
| 917 |
+
z = Integral(x, (x, 1, 1))
|
| 918 |
+
assert z != 0
|
| 919 |
+
assert simplify(z) is S.Zero
|
| 920 |
+
assert simplify(S.Zero) is S.Zero
|
| 921 |
+
z = simplify(Float(0))
|
| 922 |
+
assert z is not S.Zero and z == 0.0
|
| 923 |
+
|
| 924 |
+
|
| 925 |
+
@slow
|
| 926 |
+
def test_issue_17141_slow():
|
| 927 |
+
# Should not give RecursionError
|
| 928 |
+
assert simplify((2**acos(I+1)**2).rewrite('log')) == 2**((pi + 2*I*log(-1 +
|
| 929 |
+
sqrt(1 - 2*I) + I))**2/4)
|
| 930 |
+
|
| 931 |
+
|
| 932 |
+
def test_issue_17141():
|
| 933 |
+
# Check that there is no RecursionError
|
| 934 |
+
assert simplify(x**(1 / acos(I))) == x**(2/(pi - 2*I*log(1 + sqrt(2))))
|
| 935 |
+
assert simplify(acos(-I)**2*acos(I)**2) == \
|
| 936 |
+
log(1 + sqrt(2))**4 + pi**2*log(1 + sqrt(2))**2/2 + pi**4/16
|
| 937 |
+
assert simplify(2**acos(I)**2) == 2**((pi - 2*I*log(1 + sqrt(2)))**2/4)
|
| 938 |
+
p = 2**acos(I+1)**2
|
| 939 |
+
assert simplify(p) == p
|
| 940 |
+
|
| 941 |
+
|
| 942 |
+
def test_simplify_kroneckerdelta():
|
| 943 |
+
i, j = symbols("i j")
|
| 944 |
+
K = KroneckerDelta
|
| 945 |
+
|
| 946 |
+
assert simplify(K(i, j)) == K(i, j)
|
| 947 |
+
assert simplify(K(0, j)) == K(0, j)
|
| 948 |
+
assert simplify(K(i, 0)) == K(i, 0)
|
| 949 |
+
|
| 950 |
+
assert simplify(K(0, j).rewrite(Piecewise) * K(1, j)) == 0
|
| 951 |
+
assert simplify(K(1, i) + Piecewise((1, Eq(j, 2)), (0, True))) == K(1, i) + K(2, j)
|
| 952 |
+
|
| 953 |
+
# issue 17214
|
| 954 |
+
assert simplify(K(0, j) * K(1, j)) == 0
|
| 955 |
+
|
| 956 |
+
n = Symbol('n', integer=True)
|
| 957 |
+
assert simplify(K(0, n) * K(1, n)) == 0
|
| 958 |
+
|
| 959 |
+
M = Matrix(4, 4, lambda i, j: K(j - i, n) if i <= j else 0)
|
| 960 |
+
assert simplify(M**2) == Matrix([[K(0, n), 0, K(1, n), 0],
|
| 961 |
+
[0, K(0, n), 0, K(1, n)],
|
| 962 |
+
[0, 0, K(0, n), 0],
|
| 963 |
+
[0, 0, 0, K(0, n)]])
|
| 964 |
+
assert simplify(eye(1) * KroneckerDelta(0, n) *
|
| 965 |
+
KroneckerDelta(1, n)) == Matrix([[0]])
|
| 966 |
+
|
| 967 |
+
assert simplify(S.Infinity * KroneckerDelta(0, n) *
|
| 968 |
+
KroneckerDelta(1, n)) is S.NaN
|
| 969 |
+
|
| 970 |
+
|
| 971 |
+
def test_issue_17292():
|
| 972 |
+
assert simplify(abs(x)/abs(x**2)) == 1/abs(x)
|
| 973 |
+
# this is bigger than the issue: check that deep processing works
|
| 974 |
+
assert simplify(5*abs((x**2 - 1)/(x - 1))) == 5*Abs(x + 1)
|
| 975 |
+
|
| 976 |
+
|
| 977 |
+
def test_issue_19822():
|
| 978 |
+
expr = And(Gt(n-2, 1), Gt(n, 1))
|
| 979 |
+
assert simplify(expr) == Gt(n, 3)
|
| 980 |
+
|
| 981 |
+
|
| 982 |
+
def test_issue_18645():
|
| 983 |
+
expr = And(Ge(x, 3), Le(x, 3))
|
| 984 |
+
assert simplify(expr) == Eq(x, 3)
|
| 985 |
+
expr = And(Eq(x, 3), Le(x, 3))
|
| 986 |
+
assert simplify(expr) == Eq(x, 3)
|
| 987 |
+
|
| 988 |
+
|
| 989 |
+
@XFAIL
|
| 990 |
+
def test_issue_18642():
|
| 991 |
+
i = Symbol("i", integer=True)
|
| 992 |
+
n = Symbol("n", integer=True)
|
| 993 |
+
expr = And(Eq(i, 2 * n), Le(i, 2*n -1))
|
| 994 |
+
assert simplify(expr) == S.false
|
| 995 |
+
|
| 996 |
+
|
| 997 |
+
@XFAIL
|
| 998 |
+
def test_issue_18389():
|
| 999 |
+
n = Symbol("n", integer=True)
|
| 1000 |
+
expr = Eq(n, 0) | (n >= 1)
|
| 1001 |
+
assert simplify(expr) == Ge(n, 0)
|
| 1002 |
+
|
| 1003 |
+
|
| 1004 |
+
def test_issue_8373():
|
| 1005 |
+
x = Symbol('x', real=True)
|
| 1006 |
+
assert simplify(Or(x < 1, x >= 1)) == S.true
|
| 1007 |
+
|
| 1008 |
+
|
| 1009 |
+
def test_issue_7950():
|
| 1010 |
+
expr = And(Eq(x, 1), Eq(x, 2))
|
| 1011 |
+
assert simplify(expr) == S.false
|
| 1012 |
+
|
| 1013 |
+
|
| 1014 |
+
def test_issue_22020():
|
| 1015 |
+
expr = I*pi/2 -oo
|
| 1016 |
+
assert simplify(expr) == expr
|
| 1017 |
+
# Used to throw an error
|
| 1018 |
+
|
| 1019 |
+
|
| 1020 |
+
def test_issue_19484():
|
| 1021 |
+
assert simplify(sign(x) * Abs(x)) == x
|
| 1022 |
+
|
| 1023 |
+
e = x + sign(x + x**3)
|
| 1024 |
+
assert simplify(Abs(x + x**3)*e) == x**3 + x*Abs(x**3 + x) + x
|
| 1025 |
+
|
| 1026 |
+
e = x**2 + sign(x**3 + 1)
|
| 1027 |
+
assert simplify(Abs(x**3 + 1) * e) == x**3 + x**2*Abs(x**3 + 1) + 1
|
| 1028 |
+
|
| 1029 |
+
f = Function('f')
|
| 1030 |
+
e = x + sign(x + f(x)**3)
|
| 1031 |
+
assert simplify(Abs(x + f(x)**3) * e) == x*Abs(x + f(x)**3) + x + f(x)**3
|
| 1032 |
+
|
| 1033 |
+
|
| 1034 |
+
def test_issue_23543():
|
| 1035 |
+
# Used to give an error
|
| 1036 |
+
x, y, z = symbols("x y z", commutative=False)
|
| 1037 |
+
assert (x*(y + z/2)).simplify() == x*(2*y + z)/2
|
| 1038 |
+
|
| 1039 |
+
|
| 1040 |
+
def test_issue_11004():
|
| 1041 |
+
|
| 1042 |
+
def f(n):
|
| 1043 |
+
return sqrt(2*pi*n) * (n/E)**n
|
| 1044 |
+
|
| 1045 |
+
def m(n, k):
|
| 1046 |
+
return f(n) / (f(n/k)**k)
|
| 1047 |
+
|
| 1048 |
+
def p(n,k):
|
| 1049 |
+
return m(n, k) / (k**n)
|
| 1050 |
+
|
| 1051 |
+
N, k = symbols('N k')
|
| 1052 |
+
half = Float('0.5', 4)
|
| 1053 |
+
z = log(p(n, k) / p(n, k + 1)).expand(force=True)
|
| 1054 |
+
r = simplify(z.subs(n, N).n(4))
|
| 1055 |
+
assert r == (
|
| 1056 |
+
half*k*log(k)
|
| 1057 |
+
- half*k*log(k + 1)
|
| 1058 |
+
+ half*log(N)
|
| 1059 |
+
- half*log(k + 1)
|
| 1060 |
+
+ Float(0.9189224, 4)
|
| 1061 |
+
)
|
| 1062 |
+
|
| 1063 |
+
|
| 1064 |
+
def test_issue_19161():
|
| 1065 |
+
polynomial = Poly('x**2').simplify()
|
| 1066 |
+
assert (polynomial-x**2).simplify() == 0
|
| 1067 |
+
|
| 1068 |
+
|
| 1069 |
+
def test_issue_22210():
|
| 1070 |
+
d = Symbol('d', integer=True)
|
| 1071 |
+
expr = 2*Derivative(sin(x), (x, d))
|
| 1072 |
+
assert expr.simplify() == expr
|
| 1073 |
+
|
| 1074 |
+
|
| 1075 |
+
def test_reduce_inverses_nc_pow():
|
| 1076 |
+
x, y = symbols("x y", commutative=True)
|
| 1077 |
+
Z = symbols("Z", commutative=False)
|
| 1078 |
+
assert simplify(2**Z * y**Z) == 2**Z * y**Z
|
| 1079 |
+
assert simplify(x**Z * y**Z) == x**Z * y**Z
|
| 1080 |
+
x, y = symbols("x y", positive=True)
|
| 1081 |
+
assert expand((x*y)**Z) == x**Z * y**Z
|
| 1082 |
+
assert simplify(x**Z * y**Z) == expand((x*y)**Z)
|
| 1083 |
+
|
| 1084 |
+
def test_nc_recursion_coeff():
|
| 1085 |
+
X = symbols("X", commutative = False)
|
| 1086 |
+
assert (2 * cos(pi/3) * X).simplify() == X
|
| 1087 |
+
assert (2.0 * cos(pi/3) * X).simplify() == X
|
lib/python3.12/site-packages/sympy/simplify/tests/test_sqrtdenest.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from sympy.core.mul import Mul
|
| 2 |
+
from sympy.core.numbers import (I, Integer, Rational)
|
| 3 |
+
from sympy.core.symbol import Symbol
|
| 4 |
+
from sympy.functions.elementary.miscellaneous import (root, sqrt)
|
| 5 |
+
from sympy.functions.elementary.trigonometric import cos
|
| 6 |
+
from sympy.integrals.integrals import Integral
|
| 7 |
+
from sympy.simplify.sqrtdenest import sqrtdenest
|
| 8 |
+
from sympy.simplify.sqrtdenest import (
|
| 9 |
+
_subsets as subsets, _sqrt_numeric_denest)
|
| 10 |
+
|
| 11 |
+
r2, r3, r5, r6, r7, r10, r15, r29 = [sqrt(x) for x in (2, 3, 5, 6, 7, 10,
|
| 12 |
+
15, 29)]
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def test_sqrtdenest():
|
| 16 |
+
d = {sqrt(5 + 2 * r6): r2 + r3,
|
| 17 |
+
sqrt(5. + 2 * r6): sqrt(5. + 2 * r6),
|
| 18 |
+
sqrt(5. + 4*sqrt(5 + 2 * r6)): sqrt(5.0 + 4*r2 + 4*r3),
|
| 19 |
+
sqrt(r2): sqrt(r2),
|
| 20 |
+
sqrt(5 + r7): sqrt(5 + r7),
|
| 21 |
+
sqrt(3 + sqrt(5 + 2*r7)):
|
| 22 |
+
3*r2*(5 + 2*r7)**Rational(1, 4)/(2*sqrt(6 + 3*r7)) +
|
| 23 |
+
r2*sqrt(6 + 3*r7)/(2*(5 + 2*r7)**Rational(1, 4)),
|
| 24 |
+
sqrt(3 + 2*r3): 3**Rational(3, 4)*(r6/2 + 3*r2/2)/3}
|
| 25 |
+
for i in d:
|
| 26 |
+
assert sqrtdenest(i) == d[i], i
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def test_sqrtdenest2():
|
| 30 |
+
assert sqrtdenest(sqrt(16 - 2*r29 + 2*sqrt(55 - 10*r29))) == \
|
| 31 |
+
r5 + sqrt(11 - 2*r29)
|
| 32 |
+
e = sqrt(-r5 + sqrt(-2*r29 + 2*sqrt(-10*r29 + 55) + 16))
|
| 33 |
+
assert sqrtdenest(e) == root(-2*r29 + 11, 4)
|
| 34 |
+
r = sqrt(1 + r7)
|
| 35 |
+
assert sqrtdenest(sqrt(1 + r)) == sqrt(1 + r)
|
| 36 |
+
e = sqrt(((1 + sqrt(1 + 2*sqrt(3 + r2 + r5)))**2).expand())
|
| 37 |
+
assert sqrtdenest(e) == 1 + sqrt(1 + 2*sqrt(r2 + r5 + 3))
|
| 38 |
+
|
| 39 |
+
assert sqrtdenest(sqrt(5*r3 + 6*r2)) == \
|
| 40 |
+
sqrt(2)*root(3, 4) + root(3, 4)**3
|
| 41 |
+
|
| 42 |
+
assert sqrtdenest(sqrt(((1 + r5 + sqrt(1 + r3))**2).expand())) == \
|
| 43 |
+
1 + r5 + sqrt(1 + r3)
|
| 44 |
+
|
| 45 |
+
assert sqrtdenest(sqrt(((1 + r5 + r7 + sqrt(1 + r3))**2).expand())) == \
|
| 46 |
+
1 + sqrt(1 + r3) + r5 + r7
|
| 47 |
+
|
| 48 |
+
e = sqrt(((1 + cos(2) + cos(3) + sqrt(1 + r3))**2).expand())
|
| 49 |
+
assert sqrtdenest(e) == cos(3) + cos(2) + 1 + sqrt(1 + r3)
|
| 50 |
+
|
| 51 |
+
e = sqrt(-2*r10 + 2*r2*sqrt(-2*r10 + 11) + 14)
|
| 52 |
+
assert sqrtdenest(e) == sqrt(-2*r10 - 2*r2 + 4*r5 + 14)
|
| 53 |
+
|
| 54 |
+
# check that the result is not more complicated than the input
|
| 55 |
+
z = sqrt(-2*r29 + cos(2) + 2*sqrt(-10*r29 + 55) + 16)
|
| 56 |
+
assert sqrtdenest(z) == z
|
| 57 |
+
|
| 58 |
+
assert sqrtdenest(sqrt(r6 + sqrt(15))) == sqrt(r6 + sqrt(15))
|
| 59 |
+
|
| 60 |
+
z = sqrt(15 - 2*sqrt(31) + 2*sqrt(55 - 10*r29))
|
| 61 |
+
assert sqrtdenest(z) == z
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def test_sqrtdenest_rec():
|
| 65 |
+
assert sqrtdenest(sqrt(-4*sqrt(14) - 2*r6 + 4*sqrt(21) + 33)) == \
|
| 66 |
+
-r2 + r3 + 2*r7
|
| 67 |
+
assert sqrtdenest(sqrt(-28*r7 - 14*r5 + 4*sqrt(35) + 82)) == \
|
| 68 |
+
-7 + r5 + 2*r7
|
| 69 |
+
assert sqrtdenest(sqrt(6*r2/11 + 2*sqrt(22)/11 + 6*sqrt(11)/11 + 2)) == \
|
| 70 |
+
sqrt(11)*(r2 + 3 + sqrt(11))/11
|
| 71 |
+
assert sqrtdenest(sqrt(468*r3 + 3024*r2 + 2912*r6 + 19735)) == \
|
| 72 |
+
9*r3 + 26 + 56*r6
|
| 73 |
+
z = sqrt(-490*r3 - 98*sqrt(115) - 98*sqrt(345) - 2107)
|
| 74 |
+
assert sqrtdenest(z) == sqrt(-1)*(7*r5 + 7*r15 + 7*sqrt(23))
|
| 75 |
+
z = sqrt(-4*sqrt(14) - 2*r6 + 4*sqrt(21) + 34)
|
| 76 |
+
assert sqrtdenest(z) == z
|
| 77 |
+
assert sqrtdenest(sqrt(-8*r2 - 2*r5 + 18)) == -r10 + 1 + r2 + r5
|
| 78 |
+
assert sqrtdenest(sqrt(8*r2 + 2*r5 - 18)) == \
|
| 79 |
+
sqrt(-1)*(-r10 + 1 + r2 + r5)
|
| 80 |
+
assert sqrtdenest(sqrt(8*r2/3 + 14*r5/3 + Rational(154, 9))) == \
|
| 81 |
+
-r10/3 + r2 + r5 + 3
|
| 82 |
+
assert sqrtdenest(sqrt(sqrt(2*r6 + 5) + sqrt(2*r7 + 8))) == \
|
| 83 |
+
sqrt(1 + r2 + r3 + r7)
|
| 84 |
+
assert sqrtdenest(sqrt(4*r15 + 8*r5 + 12*r3 + 24)) == 1 + r3 + r5 + r15
|
| 85 |
+
|
| 86 |
+
w = 1 + r2 + r3 + r5 + r7
|
| 87 |
+
assert sqrtdenest(sqrt((w**2).expand())) == w
|
| 88 |
+
z = sqrt((w**2).expand() + 1)
|
| 89 |
+
assert sqrtdenest(z) == z
|
| 90 |
+
|
| 91 |
+
z = sqrt(2*r10 + 6*r2 + 4*r5 + 12 + 10*r15 + 30*r3)
|
| 92 |
+
assert sqrtdenest(z) == z
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def test_issue_6241():
|
| 96 |
+
z = sqrt( -320 + 32*sqrt(5) + 64*r15)
|
| 97 |
+
assert sqrtdenest(z) == z
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def test_sqrtdenest3():
|
| 101 |
+
z = sqrt(13 - 2*r10 + 2*r2*sqrt(-2*r10 + 11))
|
| 102 |
+
assert sqrtdenest(z) == -1 + r2 + r10
|
| 103 |
+
assert sqrtdenest(z, max_iter=1) == -1 + sqrt(2) + sqrt(10)
|
| 104 |
+
z = sqrt(sqrt(r2 + 2) + 2)
|
| 105 |
+
assert sqrtdenest(z) == z
|
| 106 |
+
assert sqrtdenest(sqrt(-2*r10 + 4*r2*sqrt(-2*r10 + 11) + 20)) == \
|
| 107 |
+
sqrt(-2*r10 - 4*r2 + 8*r5 + 20)
|
| 108 |
+
assert sqrtdenest(sqrt((112 + 70*r2) + (46 + 34*r2)*r5)) == \
|
| 109 |
+
r10 + 5 + 4*r2 + 3*r5
|
| 110 |
+
z = sqrt(5 + sqrt(2*r6 + 5)*sqrt(-2*r29 + 2*sqrt(-10*r29 + 55) + 16))
|
| 111 |
+
r = sqrt(-2*r29 + 11)
|
| 112 |
+
assert sqrtdenest(z) == sqrt(r2*r + r3*r + r10 + r15 + 5)
|
| 113 |
+
|
| 114 |
+
n = sqrt(2*r6/7 + 2*r7/7 + 2*sqrt(42)/7 + 2)
|
| 115 |
+
d = sqrt(16 - 2*r29 + 2*sqrt(55 - 10*r29))
|
| 116 |
+
assert sqrtdenest(n/d) == r7*(1 + r6 + r7)/(Mul(7, (sqrt(-2*r29 + 11) + r5),
|
| 117 |
+
evaluate=False))
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def test_sqrtdenest4():
|
| 121 |
+
# see Denest_en.pdf in https://github.com/sympy/sympy/issues/3192
|
| 122 |
+
z = sqrt(8 - r2*sqrt(5 - r5) - sqrt(3)*(1 + r5))
|
| 123 |
+
z1 = sqrtdenest(z)
|
| 124 |
+
c = sqrt(-r5 + 5)
|
| 125 |
+
z1 = ((-r15*c - r3*c + c + r5*c - r6 - r2 + r10 + sqrt(30))/4).expand()
|
| 126 |
+
assert sqrtdenest(z) == z1
|
| 127 |
+
|
| 128 |
+
z = sqrt(2*r2*sqrt(r2 + 2) + 5*r2 + 4*sqrt(r2 + 2) + 8)
|
| 129 |
+
assert sqrtdenest(z) == r2 + sqrt(r2 + 2) + 2
|
| 130 |
+
|
| 131 |
+
w = 2 + r2 + r3 + (1 + r3)*sqrt(2 + r2 + 5*r3)
|
| 132 |
+
z = sqrt((w**2).expand())
|
| 133 |
+
assert sqrtdenest(z) == w.expand()
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def test_sqrt_symbolic_denest():
|
| 137 |
+
x = Symbol('x')
|
| 138 |
+
z = sqrt(((1 + sqrt(sqrt(2 + x) + 3))**2).expand())
|
| 139 |
+
assert sqrtdenest(z) == sqrt((1 + sqrt(sqrt(2 + x) + 3))**2)
|
| 140 |
+
z = sqrt(((1 + sqrt(sqrt(2 + cos(1)) + 3))**2).expand())
|
| 141 |
+
assert sqrtdenest(z) == 1 + sqrt(sqrt(2 + cos(1)) + 3)
|
| 142 |
+
z = ((1 + cos(2))**4 + 1).expand()
|
| 143 |
+
assert sqrtdenest(z) == z
|
| 144 |
+
z = sqrt(((1 + sqrt(sqrt(2 + cos(3*x)) + 3))**2 + 1).expand())
|
| 145 |
+
assert sqrtdenest(z) == z
|
| 146 |
+
c = cos(3)
|
| 147 |
+
c2 = c**2
|
| 148 |
+
assert sqrtdenest(sqrt(2*sqrt(1 + r3)*c + c2 + 1 + r3*c2)) == \
|
| 149 |
+
-1 - sqrt(1 + r3)*c
|
| 150 |
+
ra = sqrt(1 + r3)
|
| 151 |
+
z = sqrt(20*ra*sqrt(3 + 3*r3) + 12*r3*ra*sqrt(3 + 3*r3) + 64*r3 + 112)
|
| 152 |
+
assert sqrtdenest(z) == z
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def test_issue_5857():
|
| 156 |
+
from sympy.abc import x, y
|
| 157 |
+
z = sqrt(1/(4*r3 + 7) + 1)
|
| 158 |
+
ans = (r2 + r6)/(r3 + 2)
|
| 159 |
+
assert sqrtdenest(z) == ans
|
| 160 |
+
assert sqrtdenest(1 + z) == 1 + ans
|
| 161 |
+
assert sqrtdenest(Integral(z + 1, (x, 1, 2))) == \
|
| 162 |
+
Integral(1 + ans, (x, 1, 2))
|
| 163 |
+
assert sqrtdenest(x + sqrt(y)) == x + sqrt(y)
|
| 164 |
+
ans = (r2 + r6)/(r3 + 2)
|
| 165 |
+
assert sqrtdenest(z) == ans
|
| 166 |
+
assert sqrtdenest(1 + z) == 1 + ans
|
| 167 |
+
assert sqrtdenest(Integral(z + 1, (x, 1, 2))) == \
|
| 168 |
+
Integral(1 + ans, (x, 1, 2))
|
| 169 |
+
assert sqrtdenest(x + sqrt(y)) == x + sqrt(y)
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def test_subsets():
|
| 173 |
+
assert subsets(1) == [[1]]
|
| 174 |
+
assert subsets(4) == [
|
| 175 |
+
[1, 0, 0, 0], [0, 1, 0, 0], [1, 1, 0, 0], [0, 0, 1, 0], [1, 0, 1, 0],
|
| 176 |
+
[0, 1, 1, 0], [1, 1, 1, 0], [0, 0, 0, 1], [1, 0, 0, 1], [0, 1, 0, 1],
|
| 177 |
+
[1, 1, 0, 1], [0, 0, 1, 1], [1, 0, 1, 1], [0, 1, 1, 1], [1, 1, 1, 1]]
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def test_issue_5653():
|
| 181 |
+
assert sqrtdenest(
|
| 182 |
+
sqrt(2 + sqrt(2 + sqrt(2)))) == sqrt(2 + sqrt(2 + sqrt(2)))
|
| 183 |
+
|
| 184 |
+
def test_issue_12420():
|
| 185 |
+
assert sqrtdenest((3 - sqrt(2)*sqrt(4 + 3*I) + 3*I)/2) == I
|
| 186 |
+
e = 3 - sqrt(2)*sqrt(4 + I) + 3*I
|
| 187 |
+
assert sqrtdenest(e) == e
|
| 188 |
+
|
| 189 |
+
def test_sqrt_ratcomb():
|
| 190 |
+
assert sqrtdenest(sqrt(1 + r3) + sqrt(3 + 3*r3) - sqrt(10 + 6*r3)) == 0
|
| 191 |
+
|
| 192 |
+
def test_issue_18041():
|
| 193 |
+
e = -sqrt(-2 + 2*sqrt(3)*I)
|
| 194 |
+
assert sqrtdenest(e) == -1 - sqrt(3)*I
|
| 195 |
+
|
| 196 |
+
def test_issue_19914():
|
| 197 |
+
a = Integer(-8)
|
| 198 |
+
b = Integer(-1)
|
| 199 |
+
r = Integer(63)
|
| 200 |
+
d2 = a*a - b*b*r
|
| 201 |
+
|
| 202 |
+
assert _sqrt_numeric_denest(a, b, r, d2) == \
|
| 203 |
+
sqrt(14)*I/2 + 3*sqrt(2)*I/2
|
| 204 |
+
assert sqrtdenest(sqrt(-8-sqrt(63))) == sqrt(14)*I/2 + 3*sqrt(2)*I/2
|
lib/python3.12/site-packages/sympy/simplify/tests/test_trigsimp.py
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from itertools import product
|
| 2 |
+
from sympy.core.function import (Subs, count_ops, diff, expand)
|
| 3 |
+
from sympy.core.numbers import (E, I, Rational, pi)
|
| 4 |
+
from sympy.core.singleton import S
|
| 5 |
+
from sympy.core.symbol import (Symbol, symbols)
|
| 6 |
+
from sympy.functions.elementary.exponential import (exp, log)
|
| 7 |
+
from sympy.functions.elementary.hyperbolic import (cosh, coth, sinh, tanh)
|
| 8 |
+
from sympy.functions.elementary.miscellaneous import sqrt
|
| 9 |
+
from sympy.functions.elementary.piecewise import Piecewise
|
| 10 |
+
from sympy.functions.elementary.trigonometric import (cos, cot, sin, tan)
|
| 11 |
+
from sympy.functions.elementary.trigonometric import (acos, asin, atan2)
|
| 12 |
+
from sympy.functions.elementary.trigonometric import (asec, acsc)
|
| 13 |
+
from sympy.functions.elementary.trigonometric import (acot, atan)
|
| 14 |
+
from sympy.integrals.integrals import integrate
|
| 15 |
+
from sympy.matrices.dense import Matrix
|
| 16 |
+
from sympy.simplify.simplify import simplify
|
| 17 |
+
from sympy.simplify.trigsimp import (exptrigsimp, trigsimp)
|
| 18 |
+
|
| 19 |
+
from sympy.testing.pytest import XFAIL
|
| 20 |
+
|
| 21 |
+
from sympy.abc import x, y
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_trigsimp1():
|
| 26 |
+
x, y = symbols('x,y')
|
| 27 |
+
|
| 28 |
+
assert trigsimp(1 - sin(x)**2) == cos(x)**2
|
| 29 |
+
assert trigsimp(1 - cos(x)**2) == sin(x)**2
|
| 30 |
+
assert trigsimp(sin(x)**2 + cos(x)**2) == 1
|
| 31 |
+
assert trigsimp(1 + tan(x)**2) == 1/cos(x)**2
|
| 32 |
+
assert trigsimp(1/cos(x)**2 - 1) == tan(x)**2
|
| 33 |
+
assert trigsimp(1/cos(x)**2 - tan(x)**2) == 1
|
| 34 |
+
assert trigsimp(1 + cot(x)**2) == 1/sin(x)**2
|
| 35 |
+
assert trigsimp(1/sin(x)**2 - 1) == 1/tan(x)**2
|
| 36 |
+
assert trigsimp(1/sin(x)**2 - cot(x)**2) == 1
|
| 37 |
+
|
| 38 |
+
assert trigsimp(5*cos(x)**2 + 5*sin(x)**2) == 5
|
| 39 |
+
assert trigsimp(5*cos(x/2)**2 + 2*sin(x/2)**2) == 3*cos(x)/2 + Rational(7, 2)
|
| 40 |
+
|
| 41 |
+
assert trigsimp(sin(x)/cos(x)) == tan(x)
|
| 42 |
+
assert trigsimp(2*tan(x)*cos(x)) == 2*sin(x)
|
| 43 |
+
assert trigsimp(cot(x)**3*sin(x)**3) == cos(x)**3
|
| 44 |
+
assert trigsimp(y*tan(x)**2/sin(x)**2) == y/cos(x)**2
|
| 45 |
+
assert trigsimp(cot(x)/cos(x)) == 1/sin(x)
|
| 46 |
+
|
| 47 |
+
assert trigsimp(sin(x + y) + sin(x - y)) == 2*sin(x)*cos(y)
|
| 48 |
+
assert trigsimp(sin(x + y) - sin(x - y)) == 2*sin(y)*cos(x)
|
| 49 |
+
assert trigsimp(cos(x + y) + cos(x - y)) == 2*cos(x)*cos(y)
|
| 50 |
+
assert trigsimp(cos(x + y) - cos(x - y)) == -2*sin(x)*sin(y)
|
| 51 |
+
assert trigsimp(tan(x + y) - tan(x)/(1 - tan(x)*tan(y))) == \
|
| 52 |
+
sin(y)/(-sin(y)*tan(x) + cos(y)) # -tan(y)/(tan(x)*tan(y) - 1)
|
| 53 |
+
|
| 54 |
+
assert trigsimp(sinh(x + y) + sinh(x - y)) == 2*sinh(x)*cosh(y)
|
| 55 |
+
assert trigsimp(sinh(x + y) - sinh(x - y)) == 2*sinh(y)*cosh(x)
|
| 56 |
+
assert trigsimp(cosh(x + y) + cosh(x - y)) == 2*cosh(x)*cosh(y)
|
| 57 |
+
assert trigsimp(cosh(x + y) - cosh(x - y)) == 2*sinh(x)*sinh(y)
|
| 58 |
+
assert trigsimp(tanh(x + y) - tanh(x)/(1 + tanh(x)*tanh(y))) == \
|
| 59 |
+
sinh(y)/(sinh(y)*tanh(x) + cosh(y))
|
| 60 |
+
|
| 61 |
+
assert trigsimp(cos(0.12345)**2 + sin(0.12345)**2) == 1.0
|
| 62 |
+
e = 2*sin(x)**2 + 2*cos(x)**2
|
| 63 |
+
assert trigsimp(log(e)) == log(2)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def test_trigsimp1a():
|
| 67 |
+
assert trigsimp(sin(2)**2*cos(3)*exp(2)/cos(2)**2) == tan(2)**2*cos(3)*exp(2)
|
| 68 |
+
assert trigsimp(tan(2)**2*cos(3)*exp(2)*cos(2)**2) == sin(2)**2*cos(3)*exp(2)
|
| 69 |
+
assert trigsimp(cot(2)*cos(3)*exp(2)*sin(2)) == cos(3)*exp(2)*cos(2)
|
| 70 |
+
assert trigsimp(tan(2)*cos(3)*exp(2)/sin(2)) == cos(3)*exp(2)/cos(2)
|
| 71 |
+
assert trigsimp(cot(2)*cos(3)*exp(2)/cos(2)) == cos(3)*exp(2)/sin(2)
|
| 72 |
+
assert trigsimp(cot(2)*cos(3)*exp(2)*tan(2)) == cos(3)*exp(2)
|
| 73 |
+
assert trigsimp(sinh(2)*cos(3)*exp(2)/cosh(2)) == tanh(2)*cos(3)*exp(2)
|
| 74 |
+
assert trigsimp(tanh(2)*cos(3)*exp(2)*cosh(2)) == sinh(2)*cos(3)*exp(2)
|
| 75 |
+
assert trigsimp(coth(2)*cos(3)*exp(2)*sinh(2)) == cosh(2)*cos(3)*exp(2)
|
| 76 |
+
assert trigsimp(tanh(2)*cos(3)*exp(2)/sinh(2)) == cos(3)*exp(2)/cosh(2)
|
| 77 |
+
assert trigsimp(coth(2)*cos(3)*exp(2)/cosh(2)) == cos(3)*exp(2)/sinh(2)
|
| 78 |
+
assert trigsimp(coth(2)*cos(3)*exp(2)*tanh(2)) == cos(3)*exp(2)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def test_trigsimp2():
|
| 82 |
+
x, y = symbols('x,y')
|
| 83 |
+
assert trigsimp(cos(x)**2*sin(y)**2 + cos(x)**2*cos(y)**2 + sin(x)**2,
|
| 84 |
+
recursive=True) == 1
|
| 85 |
+
assert trigsimp(sin(x)**2*sin(y)**2 + sin(x)**2*cos(y)**2 + cos(x)**2,
|
| 86 |
+
recursive=True) == 1
|
| 87 |
+
assert trigsimp(
|
| 88 |
+
Subs(x, x, sin(y)**2 + cos(y)**2)) == Subs(x, x, 1)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def test_issue_4373():
|
| 92 |
+
x = Symbol("x")
|
| 93 |
+
assert abs(trigsimp(2.0*sin(x)**2 + 2.0*cos(x)**2) - 2.0) < 1e-10
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def test_trigsimp3():
|
| 97 |
+
x, y = symbols('x,y')
|
| 98 |
+
assert trigsimp(sin(x)/cos(x)) == tan(x)
|
| 99 |
+
assert trigsimp(sin(x)**2/cos(x)**2) == tan(x)**2
|
| 100 |
+
assert trigsimp(sin(x)**3/cos(x)**3) == tan(x)**3
|
| 101 |
+
assert trigsimp(sin(x)**10/cos(x)**10) == tan(x)**10
|
| 102 |
+
|
| 103 |
+
assert trigsimp(cos(x)/sin(x)) == 1/tan(x)
|
| 104 |
+
assert trigsimp(cos(x)**2/sin(x)**2) == 1/tan(x)**2
|
| 105 |
+
assert trigsimp(cos(x)**10/sin(x)**10) == 1/tan(x)**10
|
| 106 |
+
|
| 107 |
+
assert trigsimp(tan(x)) == trigsimp(sin(x)/cos(x))
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def test_issue_4661():
|
| 111 |
+
a, x, y = symbols('a x y')
|
| 112 |
+
eq = -4*sin(x)**4 + 4*cos(x)**4 - 8*cos(x)**2
|
| 113 |
+
assert trigsimp(eq) == -4
|
| 114 |
+
n = sin(x)**6 + 4*sin(x)**4*cos(x)**2 + 5*sin(x)**2*cos(x)**4 + 2*cos(x)**6
|
| 115 |
+
d = -sin(x)**2 - 2*cos(x)**2
|
| 116 |
+
assert simplify(n/d) == -1
|
| 117 |
+
assert trigsimp(-2*cos(x)**2 + cos(x)**4 - sin(x)**4) == -1
|
| 118 |
+
eq = (- sin(x)**3/4)*cos(x) + (cos(x)**3/4)*sin(x) - sin(2*x)*cos(2*x)/8
|
| 119 |
+
assert trigsimp(eq) == 0
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def test_issue_4494():
|
| 123 |
+
a, b = symbols('a b')
|
| 124 |
+
eq = sin(a)**2*sin(b)**2 + cos(a)**2*cos(b)**2*tan(a)**2 + cos(a)**2
|
| 125 |
+
assert trigsimp(eq) == 1
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def test_issue_5948():
|
| 129 |
+
a, x, y = symbols('a x y')
|
| 130 |
+
assert trigsimp(diff(integrate(cos(x)/sin(x)**7, x), x)) == \
|
| 131 |
+
cos(x)/sin(x)**7
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
def test_issue_4775():
|
| 135 |
+
a, x, y = symbols('a x y')
|
| 136 |
+
assert trigsimp(sin(x)*cos(y)+cos(x)*sin(y)) == sin(x + y)
|
| 137 |
+
assert trigsimp(sin(x)*cos(y)+cos(x)*sin(y)+3) == sin(x + y) + 3
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def test_issue_4280():
|
| 141 |
+
a, x, y = symbols('a x y')
|
| 142 |
+
assert trigsimp(cos(x)**2 + cos(y)**2*sin(x)**2 + sin(y)**2*sin(x)**2) == 1
|
| 143 |
+
assert trigsimp(a**2*sin(x)**2 + a**2*cos(y)**2*cos(x)**2 + a**2*cos(x)**2*sin(y)**2) == a**2
|
| 144 |
+
assert trigsimp(a**2*cos(y)**2*sin(x)**2 + a**2*sin(y)**2*sin(x)**2) == a**2*sin(x)**2
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def test_issue_3210():
|
| 148 |
+
eqs = (sin(2)*cos(3) + sin(3)*cos(2),
|
| 149 |
+
-sin(2)*sin(3) + cos(2)*cos(3),
|
| 150 |
+
sin(2)*cos(3) - sin(3)*cos(2),
|
| 151 |
+
sin(2)*sin(3) + cos(2)*cos(3),
|
| 152 |
+
sin(2)*sin(3) + cos(2)*cos(3) + cos(2),
|
| 153 |
+
sinh(2)*cosh(3) + sinh(3)*cosh(2),
|
| 154 |
+
sinh(2)*sinh(3) + cosh(2)*cosh(3),
|
| 155 |
+
)
|
| 156 |
+
assert [trigsimp(e) for e in eqs] == [
|
| 157 |
+
sin(5),
|
| 158 |
+
cos(5),
|
| 159 |
+
-sin(1),
|
| 160 |
+
cos(1),
|
| 161 |
+
cos(1) + cos(2),
|
| 162 |
+
sinh(5),
|
| 163 |
+
cosh(5),
|
| 164 |
+
]
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def test_trigsimp_issues():
|
| 168 |
+
a, x, y = symbols('a x y')
|
| 169 |
+
|
| 170 |
+
# issue 4625 - factor_terms works, too
|
| 171 |
+
assert trigsimp(sin(x)**3 + cos(x)**2*sin(x)) == sin(x)
|
| 172 |
+
|
| 173 |
+
# issue 5948
|
| 174 |
+
assert trigsimp(diff(integrate(cos(x)/sin(x)**3, x), x)) == \
|
| 175 |
+
cos(x)/sin(x)**3
|
| 176 |
+
assert trigsimp(diff(integrate(sin(x)/cos(x)**3, x), x)) == \
|
| 177 |
+
sin(x)/cos(x)**3
|
| 178 |
+
|
| 179 |
+
# check integer exponents
|
| 180 |
+
e = sin(x)**y/cos(x)**y
|
| 181 |
+
assert trigsimp(e) == e
|
| 182 |
+
assert trigsimp(e.subs(y, 2)) == tan(x)**2
|
| 183 |
+
assert trigsimp(e.subs(x, 1)) == tan(1)**y
|
| 184 |
+
|
| 185 |
+
# check for multiple patterns
|
| 186 |
+
assert (cos(x)**2/sin(x)**2*cos(y)**2/sin(y)**2).trigsimp() == \
|
| 187 |
+
1/tan(x)**2/tan(y)**2
|
| 188 |
+
assert trigsimp(cos(x)/sin(x)*cos(x+y)/sin(x+y)) == \
|
| 189 |
+
1/(tan(x)*tan(x + y))
|
| 190 |
+
|
| 191 |
+
eq = cos(2)*(cos(3) + 1)**2/(cos(3) - 1)**2
|
| 192 |
+
assert trigsimp(eq) == eq.factor() # factor makes denom (-1 + cos(3))**2
|
| 193 |
+
assert trigsimp(cos(2)*(cos(3) + 1)**2*(cos(3) - 1)**2) == \
|
| 194 |
+
cos(2)*sin(3)**4
|
| 195 |
+
|
| 196 |
+
# issue 6789; this generates an expression that formerly caused
|
| 197 |
+
# trigsimp to hang
|
| 198 |
+
assert cot(x).equals(tan(x)) is False
|
| 199 |
+
|
| 200 |
+
# nan or the unchanged expression is ok, but not sin(1)
|
| 201 |
+
z = cos(x)**2 + sin(x)**2 - 1
|
| 202 |
+
z1 = tan(x)**2 - 1/cot(x)**2
|
| 203 |
+
n = (1 + z1/z)
|
| 204 |
+
assert trigsimp(sin(n)) != sin(1)
|
| 205 |
+
eq = x*(n - 1) - x*n
|
| 206 |
+
assert trigsimp(eq) is S.NaN
|
| 207 |
+
assert trigsimp(eq, recursive=True) is S.NaN
|
| 208 |
+
assert trigsimp(1).is_Integer
|
| 209 |
+
|
| 210 |
+
assert trigsimp(-sin(x)**4 - 2*sin(x)**2*cos(x)**2 - cos(x)**4) == -1
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def test_trigsimp_issue_2515():
|
| 214 |
+
x = Symbol('x')
|
| 215 |
+
assert trigsimp(x*cos(x)*tan(x)) == x*sin(x)
|
| 216 |
+
assert trigsimp(-sin(x) + cos(x)*tan(x)) == 0
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def test_trigsimp_issue_3826():
|
| 220 |
+
assert trigsimp(tan(2*x).expand(trig=True)) == tan(2*x)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def test_trigsimp_issue_4032():
|
| 224 |
+
n = Symbol('n', integer=True, positive=True)
|
| 225 |
+
assert trigsimp(2**(n/2)*cos(pi*n/4)/2 + 2**(n - 1)/2) == \
|
| 226 |
+
2**(n/2)*cos(pi*n/4)/2 + 2**n/4
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
def test_trigsimp_issue_7761():
|
| 230 |
+
assert trigsimp(cosh(pi/4)) == cosh(pi/4)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def test_trigsimp_noncommutative():
|
| 234 |
+
x, y = symbols('x,y')
|
| 235 |
+
A, B = symbols('A,B', commutative=False)
|
| 236 |
+
|
| 237 |
+
assert trigsimp(A - A*sin(x)**2) == A*cos(x)**2
|
| 238 |
+
assert trigsimp(A - A*cos(x)**2) == A*sin(x)**2
|
| 239 |
+
assert trigsimp(A*sin(x)**2 + A*cos(x)**2) == A
|
| 240 |
+
assert trigsimp(A + A*tan(x)**2) == A/cos(x)**2
|
| 241 |
+
assert trigsimp(A/cos(x)**2 - A) == A*tan(x)**2
|
| 242 |
+
assert trigsimp(A/cos(x)**2 - A*tan(x)**2) == A
|
| 243 |
+
assert trigsimp(A + A*cot(x)**2) == A/sin(x)**2
|
| 244 |
+
assert trigsimp(A/sin(x)**2 - A) == A/tan(x)**2
|
| 245 |
+
assert trigsimp(A/sin(x)**2 - A*cot(x)**2) == A
|
| 246 |
+
|
| 247 |
+
assert trigsimp(y*A*cos(x)**2 + y*A*sin(x)**2) == y*A
|
| 248 |
+
|
| 249 |
+
assert trigsimp(A*sin(x)/cos(x)) == A*tan(x)
|
| 250 |
+
assert trigsimp(A*tan(x)*cos(x)) == A*sin(x)
|
| 251 |
+
assert trigsimp(A*cot(x)**3*sin(x)**3) == A*cos(x)**3
|
| 252 |
+
assert trigsimp(y*A*tan(x)**2/sin(x)**2) == y*A/cos(x)**2
|
| 253 |
+
assert trigsimp(A*cot(x)/cos(x)) == A/sin(x)
|
| 254 |
+
|
| 255 |
+
assert trigsimp(A*sin(x + y) + A*sin(x - y)) == 2*A*sin(x)*cos(y)
|
| 256 |
+
assert trigsimp(A*sin(x + y) - A*sin(x - y)) == 2*A*sin(y)*cos(x)
|
| 257 |
+
assert trigsimp(A*cos(x + y) + A*cos(x - y)) == 2*A*cos(x)*cos(y)
|
| 258 |
+
assert trigsimp(A*cos(x + y) - A*cos(x - y)) == -2*A*sin(x)*sin(y)
|
| 259 |
+
|
| 260 |
+
assert trigsimp(A*sinh(x + y) + A*sinh(x - y)) == 2*A*sinh(x)*cosh(y)
|
| 261 |
+
assert trigsimp(A*sinh(x + y) - A*sinh(x - y)) == 2*A*sinh(y)*cosh(x)
|
| 262 |
+
assert trigsimp(A*cosh(x + y) + A*cosh(x - y)) == 2*A*cosh(x)*cosh(y)
|
| 263 |
+
assert trigsimp(A*cosh(x + y) - A*cosh(x - y)) == 2*A*sinh(x)*sinh(y)
|
| 264 |
+
|
| 265 |
+
assert trigsimp(A*cos(0.12345)**2 + A*sin(0.12345)**2) == 1.0*A
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def test_hyperbolic_simp():
|
| 269 |
+
x, y = symbols('x,y')
|
| 270 |
+
|
| 271 |
+
assert trigsimp(sinh(x)**2 + 1) == cosh(x)**2
|
| 272 |
+
assert trigsimp(cosh(x)**2 - 1) == sinh(x)**2
|
| 273 |
+
assert trigsimp(cosh(x)**2 - sinh(x)**2) == 1
|
| 274 |
+
assert trigsimp(1 - tanh(x)**2) == 1/cosh(x)**2
|
| 275 |
+
assert trigsimp(1 - 1/cosh(x)**2) == tanh(x)**2
|
| 276 |
+
assert trigsimp(tanh(x)**2 + 1/cosh(x)**2) == 1
|
| 277 |
+
assert trigsimp(coth(x)**2 - 1) == 1/sinh(x)**2
|
| 278 |
+
assert trigsimp(1/sinh(x)**2 + 1) == 1/tanh(x)**2
|
| 279 |
+
assert trigsimp(coth(x)**2 - 1/sinh(x)**2) == 1
|
| 280 |
+
|
| 281 |
+
assert trigsimp(5*cosh(x)**2 - 5*sinh(x)**2) == 5
|
| 282 |
+
assert trigsimp(5*cosh(x/2)**2 - 2*sinh(x/2)**2) == 3*cosh(x)/2 + Rational(7, 2)
|
| 283 |
+
|
| 284 |
+
assert trigsimp(sinh(x)/cosh(x)) == tanh(x)
|
| 285 |
+
assert trigsimp(tanh(x)) == trigsimp(sinh(x)/cosh(x))
|
| 286 |
+
assert trigsimp(cosh(x)/sinh(x)) == 1/tanh(x)
|
| 287 |
+
assert trigsimp(2*tanh(x)*cosh(x)) == 2*sinh(x)
|
| 288 |
+
assert trigsimp(coth(x)**3*sinh(x)**3) == cosh(x)**3
|
| 289 |
+
assert trigsimp(y*tanh(x)**2/sinh(x)**2) == y/cosh(x)**2
|
| 290 |
+
assert trigsimp(coth(x)/cosh(x)) == 1/sinh(x)
|
| 291 |
+
|
| 292 |
+
for a in (pi/6*I, pi/4*I, pi/3*I):
|
| 293 |
+
assert trigsimp(sinh(a)*cosh(x) + cosh(a)*sinh(x)) == sinh(x + a)
|
| 294 |
+
assert trigsimp(-sinh(a)*cosh(x) + cosh(a)*sinh(x)) == sinh(x - a)
|
| 295 |
+
|
| 296 |
+
e = 2*cosh(x)**2 - 2*sinh(x)**2
|
| 297 |
+
assert trigsimp(log(e)) == log(2)
|
| 298 |
+
|
| 299 |
+
# issue 19535:
|
| 300 |
+
assert trigsimp(sqrt(cosh(x)**2 - 1)) == sqrt(sinh(x)**2)
|
| 301 |
+
|
| 302 |
+
assert trigsimp(cosh(x)**2*cosh(y)**2 - cosh(x)**2*sinh(y)**2 - sinh(x)**2,
|
| 303 |
+
recursive=True) == 1
|
| 304 |
+
assert trigsimp(sinh(x)**2*sinh(y)**2 - sinh(x)**2*cosh(y)**2 + cosh(x)**2,
|
| 305 |
+
recursive=True) == 1
|
| 306 |
+
|
| 307 |
+
assert abs(trigsimp(2.0*cosh(x)**2 - 2.0*sinh(x)**2) - 2.0) < 1e-10
|
| 308 |
+
|
| 309 |
+
assert trigsimp(sinh(x)**2/cosh(x)**2) == tanh(x)**2
|
| 310 |
+
assert trigsimp(sinh(x)**3/cosh(x)**3) == tanh(x)**3
|
| 311 |
+
assert trigsimp(sinh(x)**10/cosh(x)**10) == tanh(x)**10
|
| 312 |
+
assert trigsimp(cosh(x)**3/sinh(x)**3) == 1/tanh(x)**3
|
| 313 |
+
|
| 314 |
+
assert trigsimp(cosh(x)/sinh(x)) == 1/tanh(x)
|
| 315 |
+
assert trigsimp(cosh(x)**2/sinh(x)**2) == 1/tanh(x)**2
|
| 316 |
+
assert trigsimp(cosh(x)**10/sinh(x)**10) == 1/tanh(x)**10
|
| 317 |
+
|
| 318 |
+
assert trigsimp(x*cosh(x)*tanh(x)) == x*sinh(x)
|
| 319 |
+
assert trigsimp(-sinh(x) + cosh(x)*tanh(x)) == 0
|
| 320 |
+
|
| 321 |
+
assert tan(x) != 1/cot(x) # cot doesn't auto-simplify
|
| 322 |
+
|
| 323 |
+
assert trigsimp(tan(x) - 1/cot(x)) == 0
|
| 324 |
+
assert trigsimp(3*tanh(x)**7 - 2/coth(x)**7) == tanh(x)**7
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def test_trigsimp_groebner():
|
| 328 |
+
from sympy.simplify.trigsimp import trigsimp_groebner
|
| 329 |
+
|
| 330 |
+
c = cos(x)
|
| 331 |
+
s = sin(x)
|
| 332 |
+
ex = (4*s*c + 12*s + 5*c**3 + 21*c**2 + 23*c + 15)/(
|
| 333 |
+
-s*c**2 + 2*s*c + 15*s + 7*c**3 + 31*c**2 + 37*c + 21)
|
| 334 |
+
resnum = (5*s - 5*c + 1)
|
| 335 |
+
resdenom = (8*s - 6*c)
|
| 336 |
+
results = [resnum/resdenom, (-resnum)/(-resdenom)]
|
| 337 |
+
assert trigsimp_groebner(ex) in results
|
| 338 |
+
assert trigsimp_groebner(s/c, hints=[tan]) == tan(x)
|
| 339 |
+
assert trigsimp_groebner(c*s) == c*s
|
| 340 |
+
assert trigsimp((-s + 1)/c + c/(-s + 1),
|
| 341 |
+
method='groebner') == 2/c
|
| 342 |
+
assert trigsimp((-s + 1)/c + c/(-s + 1),
|
| 343 |
+
method='groebner', polynomial=True) == 2/c
|
| 344 |
+
|
| 345 |
+
# Test quick=False works
|
| 346 |
+
assert trigsimp_groebner(ex, hints=[2]) in results
|
| 347 |
+
assert trigsimp_groebner(ex, hints=[int(2)]) in results
|
| 348 |
+
|
| 349 |
+
# test "I"
|
| 350 |
+
assert trigsimp_groebner(sin(I*x)/cos(I*x), hints=[tanh]) == I*tanh(x)
|
| 351 |
+
|
| 352 |
+
# test hyperbolic / sums
|
| 353 |
+
assert trigsimp_groebner((tanh(x)+tanh(y))/(1+tanh(x)*tanh(y)),
|
| 354 |
+
hints=[(tanh, x, y)]) == tanh(x + y)
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def test_issue_2827_trigsimp_methods():
|
| 358 |
+
measure1 = lambda expr: len(str(expr))
|
| 359 |
+
measure2 = lambda expr: -count_ops(expr)
|
| 360 |
+
# Return the most complicated result
|
| 361 |
+
expr = (x + 1)/(x + sin(x)**2 + cos(x)**2)
|
| 362 |
+
ans = Matrix([1])
|
| 363 |
+
M = Matrix([expr])
|
| 364 |
+
assert trigsimp(M, method='fu', measure=measure1) == ans
|
| 365 |
+
assert trigsimp(M, method='fu', measure=measure2) != ans
|
| 366 |
+
# all methods should work with Basic expressions even if they
|
| 367 |
+
# aren't Expr
|
| 368 |
+
M = Matrix.eye(1)
|
| 369 |
+
assert all(trigsimp(M, method=m) == M for m in
|
| 370 |
+
'fu matching groebner old'.split())
|
| 371 |
+
# watch for E in exptrigsimp, not only exp()
|
| 372 |
+
eq = 1/sqrt(E) + E
|
| 373 |
+
assert exptrigsimp(eq) == eq
|
| 374 |
+
|
| 375 |
+
def test_issue_15129_trigsimp_methods():
|
| 376 |
+
t1 = Matrix([sin(Rational(1, 50)), cos(Rational(1, 50)), 0])
|
| 377 |
+
t2 = Matrix([sin(Rational(1, 25)), cos(Rational(1, 25)), 0])
|
| 378 |
+
t3 = Matrix([cos(Rational(1, 25)), sin(Rational(1, 25)), 0])
|
| 379 |
+
r1 = t1.dot(t2)
|
| 380 |
+
r2 = t1.dot(t3)
|
| 381 |
+
assert trigsimp(r1) == cos(Rational(1, 50))
|
| 382 |
+
assert trigsimp(r2) == sin(Rational(3, 50))
|
| 383 |
+
|
| 384 |
+
def test_exptrigsimp():
|
| 385 |
+
def valid(a, b):
|
| 386 |
+
from sympy.core.random import verify_numerically as tn
|
| 387 |
+
if not (tn(a, b) and a == b):
|
| 388 |
+
return False
|
| 389 |
+
return True
|
| 390 |
+
|
| 391 |
+
assert exptrigsimp(exp(x) + exp(-x)) == 2*cosh(x)
|
| 392 |
+
assert exptrigsimp(exp(x) - exp(-x)) == 2*sinh(x)
|
| 393 |
+
assert exptrigsimp((2*exp(x)-2*exp(-x))/(exp(x)+exp(-x))) == 2*tanh(x)
|
| 394 |
+
assert exptrigsimp((2*exp(2*x)-2)/(exp(2*x)+1)) == 2*tanh(x)
|
| 395 |
+
e = [cos(x) + I*sin(x), cos(x) - I*sin(x),
|
| 396 |
+
cosh(x) - sinh(x), cosh(x) + sinh(x)]
|
| 397 |
+
ok = [exp(I*x), exp(-I*x), exp(-x), exp(x)]
|
| 398 |
+
assert all(valid(i, j) for i, j in zip(
|
| 399 |
+
[exptrigsimp(ei) for ei in e], ok))
|
| 400 |
+
|
| 401 |
+
ue = [cos(x) + sin(x), cos(x) - sin(x),
|
| 402 |
+
cosh(x) + I*sinh(x), cosh(x) - I*sinh(x)]
|
| 403 |
+
assert [exptrigsimp(ei) == ei for ei in ue]
|
| 404 |
+
|
| 405 |
+
res = []
|
| 406 |
+
ok = [y*tanh(1), 1/(y*tanh(1)), I*y*tan(1), -I/(y*tan(1)),
|
| 407 |
+
y*tanh(x), 1/(y*tanh(x)), I*y*tan(x), -I/(y*tan(x)),
|
| 408 |
+
y*tanh(1 + I), 1/(y*tanh(1 + I))]
|
| 409 |
+
for a in (1, I, x, I*x, 1 + I):
|
| 410 |
+
w = exp(a)
|
| 411 |
+
eq = y*(w - 1/w)/(w + 1/w)
|
| 412 |
+
res.append(simplify(eq))
|
| 413 |
+
res.append(simplify(1/eq))
|
| 414 |
+
assert all(valid(i, j) for i, j in zip(res, ok))
|
| 415 |
+
|
| 416 |
+
for a in range(1, 3):
|
| 417 |
+
w = exp(a)
|
| 418 |
+
e = w + 1/w
|
| 419 |
+
s = simplify(e)
|
| 420 |
+
assert s == exptrigsimp(e)
|
| 421 |
+
assert valid(s, 2*cosh(a))
|
| 422 |
+
e = w - 1/w
|
| 423 |
+
s = simplify(e)
|
| 424 |
+
assert s == exptrigsimp(e)
|
| 425 |
+
assert valid(s, 2*sinh(a))
|
| 426 |
+
|
| 427 |
+
def test_exptrigsimp_noncommutative():
|
| 428 |
+
a,b = symbols('a b', commutative=False)
|
| 429 |
+
x = Symbol('x', commutative=True)
|
| 430 |
+
assert exp(a + x) == exptrigsimp(exp(a)*exp(x))
|
| 431 |
+
p = exp(a)*exp(b) - exp(b)*exp(a)
|
| 432 |
+
assert p == exptrigsimp(p) != 0
|
| 433 |
+
|
| 434 |
+
def test_powsimp_on_numbers():
|
| 435 |
+
assert 2**(Rational(1, 3) - 2) == 2**Rational(1, 3)/4
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
@XFAIL
|
| 439 |
+
def test_issue_6811_fail():
|
| 440 |
+
# from doc/src/modules/physics/mechanics/examples.rst, the current `eq`
|
| 441 |
+
# at Line 576 (in different variables) was formerly the equivalent and
|
| 442 |
+
# shorter expression given below...it would be nice to get the short one
|
| 443 |
+
# back again
|
| 444 |
+
xp, y, x, z = symbols('xp, y, x, z')
|
| 445 |
+
eq = 4*(-19*sin(x)*y + 5*sin(3*x)*y + 15*cos(2*x)*z - 21*z)*xp/(9*cos(x) - 5*cos(3*x))
|
| 446 |
+
assert trigsimp(eq) == -2*(2*cos(x)*tan(x)*y + 3*z)*xp/cos(x)
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
def test_Piecewise():
|
| 450 |
+
e1 = x*(x + y) - y*(x + y)
|
| 451 |
+
e2 = sin(x)**2 + cos(x)**2
|
| 452 |
+
e3 = expand((x + y)*y/x)
|
| 453 |
+
# s1 = simplify(e1)
|
| 454 |
+
s2 = simplify(e2)
|
| 455 |
+
# s3 = simplify(e3)
|
| 456 |
+
|
| 457 |
+
# trigsimp tries not to touch non-trig containing args
|
| 458 |
+
assert trigsimp(Piecewise((e1, e3 < e2), (e3, True))) == \
|
| 459 |
+
Piecewise((e1, e3 < s2), (e3, True))
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def test_issue_21594():
|
| 463 |
+
assert simplify(exp(Rational(1,2)) + exp(Rational(-1,2))) == cosh(S.Half)*2
|
| 464 |
+
|
| 465 |
+
|
| 466 |
+
def test_trigsimp_old():
|
| 467 |
+
x, y = symbols('x,y')
|
| 468 |
+
|
| 469 |
+
assert trigsimp(1 - sin(x)**2, old=True) == cos(x)**2
|
| 470 |
+
assert trigsimp(1 - cos(x)**2, old=True) == sin(x)**2
|
| 471 |
+
assert trigsimp(sin(x)**2 + cos(x)**2, old=True) == 1
|
| 472 |
+
assert trigsimp(1 + tan(x)**2, old=True) == 1/cos(x)**2
|
| 473 |
+
assert trigsimp(1/cos(x)**2 - 1, old=True) == tan(x)**2
|
| 474 |
+
assert trigsimp(1/cos(x)**2 - tan(x)**2, old=True) == 1
|
| 475 |
+
assert trigsimp(1 + cot(x)**2, old=True) == 1/sin(x)**2
|
| 476 |
+
assert trigsimp(1/sin(x)**2 - cot(x)**2, old=True) == 1
|
| 477 |
+
|
| 478 |
+
assert trigsimp(5*cos(x)**2 + 5*sin(x)**2, old=True) == 5
|
| 479 |
+
|
| 480 |
+
assert trigsimp(sin(x)/cos(x), old=True) == tan(x)
|
| 481 |
+
assert trigsimp(2*tan(x)*cos(x), old=True) == 2*sin(x)
|
| 482 |
+
assert trigsimp(cot(x)**3*sin(x)**3, old=True) == cos(x)**3
|
| 483 |
+
assert trigsimp(y*tan(x)**2/sin(x)**2, old=True) == y/cos(x)**2
|
| 484 |
+
assert trigsimp(cot(x)/cos(x), old=True) == 1/sin(x)
|
| 485 |
+
|
| 486 |
+
assert trigsimp(sin(x + y) + sin(x - y), old=True) == 2*sin(x)*cos(y)
|
| 487 |
+
assert trigsimp(sin(x + y) - sin(x - y), old=True) == 2*sin(y)*cos(x)
|
| 488 |
+
assert trigsimp(cos(x + y) + cos(x - y), old=True) == 2*cos(x)*cos(y)
|
| 489 |
+
assert trigsimp(cos(x + y) - cos(x - y), old=True) == -2*sin(x)*sin(y)
|
| 490 |
+
|
| 491 |
+
assert trigsimp(sinh(x + y) + sinh(x - y), old=True) == 2*sinh(x)*cosh(y)
|
| 492 |
+
assert trigsimp(sinh(x + y) - sinh(x - y), old=True) == 2*sinh(y)*cosh(x)
|
| 493 |
+
assert trigsimp(cosh(x + y) + cosh(x - y), old=True) == 2*cosh(x)*cosh(y)
|
| 494 |
+
assert trigsimp(cosh(x + y) - cosh(x - y), old=True) == 2*sinh(x)*sinh(y)
|
| 495 |
+
|
| 496 |
+
assert trigsimp(cos(0.12345)**2 + sin(0.12345)**2, old=True) == 1.0
|
| 497 |
+
|
| 498 |
+
assert trigsimp(sin(x)/cos(x), old=True, method='combined') == tan(x)
|
| 499 |
+
assert trigsimp(sin(x)/cos(x), old=True, method='groebner') == sin(x)/cos(x)
|
| 500 |
+
assert trigsimp(sin(x)/cos(x), old=True, method='groebner', hints=[tan]) == tan(x)
|
| 501 |
+
|
| 502 |
+
assert trigsimp(1-sin(sin(x)**2+cos(x)**2)**2, old=True, deep=True) == cos(1)**2
|
| 503 |
+
|
| 504 |
+
|
| 505 |
+
def test_trigsimp_inverse():
|
| 506 |
+
alpha = symbols('alpha')
|
| 507 |
+
s, c = sin(alpha), cos(alpha)
|
| 508 |
+
|
| 509 |
+
for finv in [asin, acos, asec, acsc, atan, acot]:
|
| 510 |
+
f = finv.inverse(None)
|
| 511 |
+
assert alpha == trigsimp(finv(f(alpha)), inverse=True)
|
| 512 |
+
|
| 513 |
+
# test atan2(cos, sin), atan2(sin, cos), etc...
|
| 514 |
+
for a, b in [[c, s], [s, c]]:
|
| 515 |
+
for i, j in product([-1, 1], repeat=2):
|
| 516 |
+
angle = atan2(i*b, j*a)
|
| 517 |
+
angle_inverted = trigsimp(angle, inverse=True)
|
| 518 |
+
assert angle_inverted != angle # assures simplification happened
|
| 519 |
+
assert sin(angle_inverted) == trigsimp(sin(angle))
|
| 520 |
+
assert cos(angle_inverted) == trigsimp(cos(angle))
|
lib/python3.12/site-packages/sympy/simplify/trigsimp.py
ADDED
|
@@ -0,0 +1,1253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections import defaultdict
|
| 2 |
+
from functools import reduce
|
| 3 |
+
|
| 4 |
+
from sympy.core import (sympify, Basic, S, Expr, factor_terms,
|
| 5 |
+
Mul, Add, bottom_up)
|
| 6 |
+
from sympy.core.cache import cacheit
|
| 7 |
+
from sympy.core.function import (count_ops, _mexpand, FunctionClass, expand,
|
| 8 |
+
expand_mul, _coeff_isneg, Derivative)
|
| 9 |
+
from sympy.core.numbers import I, Integer
|
| 10 |
+
from sympy.core.intfunc import igcd
|
| 11 |
+
from sympy.core.sorting import _nodes
|
| 12 |
+
from sympy.core.symbol import Dummy, symbols, Wild
|
| 13 |
+
from sympy.external.gmpy import SYMPY_INTS
|
| 14 |
+
from sympy.functions import sin, cos, exp, cosh, tanh, sinh, tan, cot, coth
|
| 15 |
+
from sympy.functions import atan2
|
| 16 |
+
from sympy.functions.elementary.hyperbolic import HyperbolicFunction
|
| 17 |
+
from sympy.functions.elementary.trigonometric import TrigonometricFunction
|
| 18 |
+
from sympy.polys import Poly, factor, cancel, parallel_poly_from_expr
|
| 19 |
+
from sympy.polys.domains import ZZ
|
| 20 |
+
from sympy.polys.polyerrors import PolificationFailed
|
| 21 |
+
from sympy.polys.polytools import groebner
|
| 22 |
+
from sympy.simplify.cse_main import cse
|
| 23 |
+
from sympy.strategies.core import identity
|
| 24 |
+
from sympy.strategies.tree import greedy
|
| 25 |
+
from sympy.utilities.iterables import iterable
|
| 26 |
+
from sympy.utilities.misc import debug
|
| 27 |
+
|
| 28 |
+
def trigsimp_groebner(expr, hints=[], quick=False, order="grlex",
|
| 29 |
+
polynomial=False):
|
| 30 |
+
"""
|
| 31 |
+
Simplify trigonometric expressions using a groebner basis algorithm.
|
| 32 |
+
|
| 33 |
+
Explanation
|
| 34 |
+
===========
|
| 35 |
+
|
| 36 |
+
This routine takes a fraction involving trigonometric or hyperbolic
|
| 37 |
+
expressions, and tries to simplify it. The primary metric is the
|
| 38 |
+
total degree. Some attempts are made to choose the simplest possible
|
| 39 |
+
expression of the minimal degree, but this is non-rigorous, and also
|
| 40 |
+
very slow (see the ``quick=True`` option).
|
| 41 |
+
|
| 42 |
+
If ``polynomial`` is set to True, instead of simplifying numerator and
|
| 43 |
+
denominator together, this function just brings numerator and denominator
|
| 44 |
+
into a canonical form. This is much faster, but has potentially worse
|
| 45 |
+
results. However, if the input is a polynomial, then the result is
|
| 46 |
+
guaranteed to be an equivalent polynomial of minimal degree.
|
| 47 |
+
|
| 48 |
+
The most important option is hints. Its entries can be any of the
|
| 49 |
+
following:
|
| 50 |
+
|
| 51 |
+
- a natural number
|
| 52 |
+
- a function
|
| 53 |
+
- an iterable of the form (func, var1, var2, ...)
|
| 54 |
+
- anything else, interpreted as a generator
|
| 55 |
+
|
| 56 |
+
A number is used to indicate that the search space should be increased.
|
| 57 |
+
A function is used to indicate that said function is likely to occur in a
|
| 58 |
+
simplified expression.
|
| 59 |
+
An iterable is used indicate that func(var1 + var2 + ...) is likely to
|
| 60 |
+
occur in a simplified .
|
| 61 |
+
An additional generator also indicates that it is likely to occur.
|
| 62 |
+
(See examples below).
|
| 63 |
+
|
| 64 |
+
This routine carries out various computationally intensive algorithms.
|
| 65 |
+
The option ``quick=True`` can be used to suppress one particularly slow
|
| 66 |
+
step (at the expense of potentially more complicated results, but never at
|
| 67 |
+
the expense of increased total degree).
|
| 68 |
+
|
| 69 |
+
Examples
|
| 70 |
+
========
|
| 71 |
+
|
| 72 |
+
>>> from sympy.abc import x, y
|
| 73 |
+
>>> from sympy import sin, tan, cos, sinh, cosh, tanh
|
| 74 |
+
>>> from sympy.simplify.trigsimp import trigsimp_groebner
|
| 75 |
+
|
| 76 |
+
Suppose you want to simplify ``sin(x)*cos(x)``. Naively, nothing happens:
|
| 77 |
+
|
| 78 |
+
>>> ex = sin(x)*cos(x)
|
| 79 |
+
>>> trigsimp_groebner(ex)
|
| 80 |
+
sin(x)*cos(x)
|
| 81 |
+
|
| 82 |
+
This is because ``trigsimp_groebner`` only looks for a simplification
|
| 83 |
+
involving just ``sin(x)`` and ``cos(x)``. You can tell it to also try
|
| 84 |
+
``2*x`` by passing ``hints=[2]``:
|
| 85 |
+
|
| 86 |
+
>>> trigsimp_groebner(ex, hints=[2])
|
| 87 |
+
sin(2*x)/2
|
| 88 |
+
>>> trigsimp_groebner(sin(x)**2 - cos(x)**2, hints=[2])
|
| 89 |
+
-cos(2*x)
|
| 90 |
+
|
| 91 |
+
Increasing the search space this way can quickly become expensive. A much
|
| 92 |
+
faster way is to give a specific expression that is likely to occur:
|
| 93 |
+
|
| 94 |
+
>>> trigsimp_groebner(ex, hints=[sin(2*x)])
|
| 95 |
+
sin(2*x)/2
|
| 96 |
+
|
| 97 |
+
Hyperbolic expressions are similarly supported:
|
| 98 |
+
|
| 99 |
+
>>> trigsimp_groebner(sinh(2*x)/sinh(x))
|
| 100 |
+
2*cosh(x)
|
| 101 |
+
|
| 102 |
+
Note how no hints had to be passed, since the expression already involved
|
| 103 |
+
``2*x``.
|
| 104 |
+
|
| 105 |
+
The tangent function is also supported. You can either pass ``tan`` in the
|
| 106 |
+
hints, to indicate that tan should be tried whenever cosine or sine are,
|
| 107 |
+
or you can pass a specific generator:
|
| 108 |
+
|
| 109 |
+
>>> trigsimp_groebner(sin(x)/cos(x), hints=[tan])
|
| 110 |
+
tan(x)
|
| 111 |
+
>>> trigsimp_groebner(sinh(x)/cosh(x), hints=[tanh(x)])
|
| 112 |
+
tanh(x)
|
| 113 |
+
|
| 114 |
+
Finally, you can use the iterable form to suggest that angle sum formulae
|
| 115 |
+
should be tried:
|
| 116 |
+
|
| 117 |
+
>>> ex = (tan(x) + tan(y))/(1 - tan(x)*tan(y))
|
| 118 |
+
>>> trigsimp_groebner(ex, hints=[(tan, x, y)])
|
| 119 |
+
tan(x + y)
|
| 120 |
+
"""
|
| 121 |
+
# TODO
|
| 122 |
+
# - preprocess by replacing everything by funcs we can handle
|
| 123 |
+
# - optionally use cot instead of tan
|
| 124 |
+
# - more intelligent hinting.
|
| 125 |
+
# For example, if the ideal is small, and we have sin(x), sin(y),
|
| 126 |
+
# add sin(x + y) automatically... ?
|
| 127 |
+
# - algebraic numbers ...
|
| 128 |
+
# - expressions of lowest degree are not distinguished properly
|
| 129 |
+
# e.g. 1 - sin(x)**2
|
| 130 |
+
# - we could try to order the generators intelligently, so as to influence
|
| 131 |
+
# which monomials appear in the quotient basis
|
| 132 |
+
|
| 133 |
+
# THEORY
|
| 134 |
+
# ------
|
| 135 |
+
# Ratsimpmodprime above can be used to "simplify" a rational function
|
| 136 |
+
# modulo a prime ideal. "Simplify" mainly means finding an equivalent
|
| 137 |
+
# expression of lower total degree.
|
| 138 |
+
#
|
| 139 |
+
# We intend to use this to simplify trigonometric functions. To do that,
|
| 140 |
+
# we need to decide (a) which ring to use, and (b) modulo which ideal to
|
| 141 |
+
# simplify. In practice, (a) means settling on a list of "generators"
|
| 142 |
+
# a, b, c, ..., such that the fraction we want to simplify is a rational
|
| 143 |
+
# function in a, b, c, ..., with coefficients in ZZ (integers).
|
| 144 |
+
# (2) means that we have to decide what relations to impose on the
|
| 145 |
+
# generators. There are two practical problems:
|
| 146 |
+
# (1) The ideal has to be *prime* (a technical term).
|
| 147 |
+
# (2) The relations have to be polynomials in the generators.
|
| 148 |
+
#
|
| 149 |
+
# We typically have two kinds of generators:
|
| 150 |
+
# - trigonometric expressions, like sin(x), cos(5*x), etc
|
| 151 |
+
# - "everything else", like gamma(x), pi, etc.
|
| 152 |
+
#
|
| 153 |
+
# Since this function is trigsimp, we will concentrate on what to do with
|
| 154 |
+
# trigonometric expressions. We can also simplify hyperbolic expressions,
|
| 155 |
+
# but the extensions should be clear.
|
| 156 |
+
#
|
| 157 |
+
# One crucial point is that all *other* generators really should behave
|
| 158 |
+
# like indeterminates. In particular if (say) "I" is one of them, then
|
| 159 |
+
# in fact I**2 + 1 = 0 and we may and will compute non-sensical
|
| 160 |
+
# expressions. However, we can work with a dummy and add the relation
|
| 161 |
+
# I**2 + 1 = 0 to our ideal, then substitute back in the end.
|
| 162 |
+
#
|
| 163 |
+
# Now regarding trigonometric generators. We split them into groups,
|
| 164 |
+
# according to the argument of the trigonometric functions. We want to
|
| 165 |
+
# organise this in such a way that most trigonometric identities apply in
|
| 166 |
+
# the same group. For example, given sin(x), cos(2*x) and cos(y), we would
|
| 167 |
+
# group as [sin(x), cos(2*x)] and [cos(y)].
|
| 168 |
+
#
|
| 169 |
+
# Our prime ideal will be built in three steps:
|
| 170 |
+
# (1) For each group, compute a "geometrically prime" ideal of relations.
|
| 171 |
+
# Geometrically prime means that it generates a prime ideal in
|
| 172 |
+
# CC[gens], not just ZZ[gens].
|
| 173 |
+
# (2) Take the union of all the generators of the ideals for all groups.
|
| 174 |
+
# By the geometric primality condition, this is still prime.
|
| 175 |
+
# (3) Add further inter-group relations which preserve primality.
|
| 176 |
+
#
|
| 177 |
+
# Step (1) works as follows. We will isolate common factors in the
|
| 178 |
+
# argument, so that all our generators are of the form sin(n*x), cos(n*x)
|
| 179 |
+
# or tan(n*x), with n an integer. Suppose first there are no tan terms.
|
| 180 |
+
# The ideal [sin(x)**2 + cos(x)**2 - 1] is geometrically prime, since
|
| 181 |
+
# X**2 + Y**2 - 1 is irreducible over CC.
|
| 182 |
+
# Now, if we have a generator sin(n*x), than we can, using trig identities,
|
| 183 |
+
# express sin(n*x) as a polynomial in sin(x) and cos(x). We can add this
|
| 184 |
+
# relation to the ideal, preserving geometric primality, since the quotient
|
| 185 |
+
# ring is unchanged.
|
| 186 |
+
# Thus we have treated all sin and cos terms.
|
| 187 |
+
# For tan(n*x), we add a relation tan(n*x)*cos(n*x) - sin(n*x) = 0.
|
| 188 |
+
# (This requires of course that we already have relations for cos(n*x) and
|
| 189 |
+
# sin(n*x).) It is not obvious, but it seems that this preserves geometric
|
| 190 |
+
# primality.
|
| 191 |
+
# XXX A real proof would be nice. HELP!
|
| 192 |
+
# Sketch that <S**2 + C**2 - 1, C*T - S> is a prime ideal of
|
| 193 |
+
# CC[S, C, T]:
|
| 194 |
+
# - it suffices to show that the projective closure in CP**3 is
|
| 195 |
+
# irreducible
|
| 196 |
+
# - using the half-angle substitutions, we can express sin(x), tan(x),
|
| 197 |
+
# cos(x) as rational functions in tan(x/2)
|
| 198 |
+
# - from this, we get a rational map from CP**1 to our curve
|
| 199 |
+
# - this is a morphism, hence the curve is prime
|
| 200 |
+
#
|
| 201 |
+
# Step (2) is trivial.
|
| 202 |
+
#
|
| 203 |
+
# Step (3) works by adding selected relations of the form
|
| 204 |
+
# sin(x + y) - sin(x)*cos(y) - sin(y)*cos(x), etc. Geometric primality is
|
| 205 |
+
# preserved by the same argument as before.
|
| 206 |
+
|
| 207 |
+
def parse_hints(hints):
|
| 208 |
+
"""Split hints into (n, funcs, iterables, gens)."""
|
| 209 |
+
n = 1
|
| 210 |
+
funcs, iterables, gens = [], [], []
|
| 211 |
+
for e in hints:
|
| 212 |
+
if isinstance(e, (SYMPY_INTS, Integer)):
|
| 213 |
+
n = e
|
| 214 |
+
elif isinstance(e, FunctionClass):
|
| 215 |
+
funcs.append(e)
|
| 216 |
+
elif iterable(e):
|
| 217 |
+
iterables.append((e[0], e[1:]))
|
| 218 |
+
# XXX sin(x+2y)?
|
| 219 |
+
# Note: we go through polys so e.g.
|
| 220 |
+
# sin(-x) -> -sin(x) -> sin(x)
|
| 221 |
+
gens.extend(parallel_poly_from_expr(
|
| 222 |
+
[e[0](x) for x in e[1:]] + [e[0](Add(*e[1:]))])[1].gens)
|
| 223 |
+
else:
|
| 224 |
+
gens.append(e)
|
| 225 |
+
return n, funcs, iterables, gens
|
| 226 |
+
|
| 227 |
+
def build_ideal(x, terms):
|
| 228 |
+
"""
|
| 229 |
+
Build generators for our ideal. ``Terms`` is an iterable with elements of
|
| 230 |
+
the form (fn, coeff), indicating that we have a generator fn(coeff*x).
|
| 231 |
+
|
| 232 |
+
If any of the terms is trigonometric, sin(x) and cos(x) are guaranteed
|
| 233 |
+
to appear in terms. Similarly for hyperbolic functions. For tan(n*x),
|
| 234 |
+
sin(n*x) and cos(n*x) are guaranteed.
|
| 235 |
+
"""
|
| 236 |
+
I = []
|
| 237 |
+
y = Dummy('y')
|
| 238 |
+
for fn, coeff in terms:
|
| 239 |
+
for c, s, t, rel in (
|
| 240 |
+
[cos, sin, tan, cos(x)**2 + sin(x)**2 - 1],
|
| 241 |
+
[cosh, sinh, tanh, cosh(x)**2 - sinh(x)**2 - 1]):
|
| 242 |
+
if coeff == 1 and fn in [c, s]:
|
| 243 |
+
I.append(rel)
|
| 244 |
+
elif fn == t:
|
| 245 |
+
I.append(t(coeff*x)*c(coeff*x) - s(coeff*x))
|
| 246 |
+
elif fn in [c, s]:
|
| 247 |
+
cn = fn(coeff*y).expand(trig=True).subs(y, x)
|
| 248 |
+
I.append(fn(coeff*x) - cn)
|
| 249 |
+
return list(set(I))
|
| 250 |
+
|
| 251 |
+
def analyse_gens(gens, hints):
|
| 252 |
+
"""
|
| 253 |
+
Analyse the generators ``gens``, using the hints ``hints``.
|
| 254 |
+
|
| 255 |
+
The meaning of ``hints`` is described in the main docstring.
|
| 256 |
+
Return a new list of generators, and also the ideal we should
|
| 257 |
+
work with.
|
| 258 |
+
"""
|
| 259 |
+
# First parse the hints
|
| 260 |
+
n, funcs, iterables, extragens = parse_hints(hints)
|
| 261 |
+
debug('n=%s funcs: %s iterables: %s extragens: %s',
|
| 262 |
+
(funcs, iterables, extragens))
|
| 263 |
+
|
| 264 |
+
# We just add the extragens to gens and analyse them as before
|
| 265 |
+
gens = list(gens)
|
| 266 |
+
gens.extend(extragens)
|
| 267 |
+
|
| 268 |
+
# remove duplicates
|
| 269 |
+
funcs = list(set(funcs))
|
| 270 |
+
iterables = list(set(iterables))
|
| 271 |
+
gens = list(set(gens))
|
| 272 |
+
|
| 273 |
+
# all the functions we can do anything with
|
| 274 |
+
allfuncs = {sin, cos, tan, sinh, cosh, tanh}
|
| 275 |
+
# sin(3*x) -> ((3, x), sin)
|
| 276 |
+
trigterms = [(g.args[0].as_coeff_mul(), g.func) for g in gens
|
| 277 |
+
if g.func in allfuncs]
|
| 278 |
+
# Our list of new generators - start with anything that we cannot
|
| 279 |
+
# work with (i.e. is not a trigonometric term)
|
| 280 |
+
freegens = [g for g in gens if g.func not in allfuncs]
|
| 281 |
+
newgens = []
|
| 282 |
+
trigdict = {}
|
| 283 |
+
for (coeff, var), fn in trigterms:
|
| 284 |
+
trigdict.setdefault(var, []).append((coeff, fn))
|
| 285 |
+
res = [] # the ideal
|
| 286 |
+
|
| 287 |
+
for key, val in trigdict.items():
|
| 288 |
+
# We have now assembeled a dictionary. Its keys are common
|
| 289 |
+
# arguments in trigonometric expressions, and values are lists of
|
| 290 |
+
# pairs (fn, coeff). x0, (fn, coeff) in trigdict means that we
|
| 291 |
+
# need to deal with fn(coeff*x0). We take the rational gcd of the
|
| 292 |
+
# coeffs, call it ``gcd``. We then use x = x0/gcd as "base symbol",
|
| 293 |
+
# all other arguments are integral multiples thereof.
|
| 294 |
+
# We will build an ideal which works with sin(x), cos(x).
|
| 295 |
+
# If hint tan is provided, also work with tan(x). Moreover, if
|
| 296 |
+
# n > 1, also work with sin(k*x) for k <= n, and similarly for cos
|
| 297 |
+
# (and tan if the hint is provided). Finally, any generators which
|
| 298 |
+
# the ideal does not work with but we need to accommodate (either
|
| 299 |
+
# because it was in expr or because it was provided as a hint)
|
| 300 |
+
# we also build into the ideal.
|
| 301 |
+
# This selection process is expressed in the list ``terms``.
|
| 302 |
+
# build_ideal then generates the actual relations in our ideal,
|
| 303 |
+
# from this list.
|
| 304 |
+
fns = [x[1] for x in val]
|
| 305 |
+
val = [x[0] for x in val]
|
| 306 |
+
gcd = reduce(igcd, val)
|
| 307 |
+
terms = [(fn, v/gcd) for (fn, v) in zip(fns, val)]
|
| 308 |
+
fs = set(funcs + fns)
|
| 309 |
+
for c, s, t in ([cos, sin, tan], [cosh, sinh, tanh]):
|
| 310 |
+
if any(x in fs for x in (c, s, t)):
|
| 311 |
+
fs.add(c)
|
| 312 |
+
fs.add(s)
|
| 313 |
+
for fn in fs:
|
| 314 |
+
for k in range(1, n + 1):
|
| 315 |
+
terms.append((fn, k))
|
| 316 |
+
extra = []
|
| 317 |
+
for fn, v in terms:
|
| 318 |
+
if fn == tan:
|
| 319 |
+
extra.append((sin, v))
|
| 320 |
+
extra.append((cos, v))
|
| 321 |
+
if fn in [sin, cos] and tan in fs:
|
| 322 |
+
extra.append((tan, v))
|
| 323 |
+
if fn == tanh:
|
| 324 |
+
extra.append((sinh, v))
|
| 325 |
+
extra.append((cosh, v))
|
| 326 |
+
if fn in [sinh, cosh] and tanh in fs:
|
| 327 |
+
extra.append((tanh, v))
|
| 328 |
+
terms.extend(extra)
|
| 329 |
+
x = gcd*Mul(*key)
|
| 330 |
+
r = build_ideal(x, terms)
|
| 331 |
+
res.extend(r)
|
| 332 |
+
newgens.extend({fn(v*x) for fn, v in terms})
|
| 333 |
+
|
| 334 |
+
# Add generators for compound expressions from iterables
|
| 335 |
+
for fn, args in iterables:
|
| 336 |
+
if fn == tan:
|
| 337 |
+
# Tan expressions are recovered from sin and cos.
|
| 338 |
+
iterables.extend([(sin, args), (cos, args)])
|
| 339 |
+
elif fn == tanh:
|
| 340 |
+
# Tanh expressions are recovered from sihn and cosh.
|
| 341 |
+
iterables.extend([(sinh, args), (cosh, args)])
|
| 342 |
+
else:
|
| 343 |
+
dummys = symbols('d:%i' % len(args), cls=Dummy)
|
| 344 |
+
expr = fn( Add(*dummys)).expand(trig=True).subs(list(zip(dummys, args)))
|
| 345 |
+
res.append(fn(Add(*args)) - expr)
|
| 346 |
+
|
| 347 |
+
if myI in gens:
|
| 348 |
+
res.append(myI**2 + 1)
|
| 349 |
+
freegens.remove(myI)
|
| 350 |
+
newgens.append(myI)
|
| 351 |
+
|
| 352 |
+
return res, freegens, newgens
|
| 353 |
+
|
| 354 |
+
myI = Dummy('I')
|
| 355 |
+
expr = expr.subs(S.ImaginaryUnit, myI)
|
| 356 |
+
subs = [(myI, S.ImaginaryUnit)]
|
| 357 |
+
|
| 358 |
+
num, denom = cancel(expr).as_numer_denom()
|
| 359 |
+
try:
|
| 360 |
+
(pnum, pdenom), opt = parallel_poly_from_expr([num, denom])
|
| 361 |
+
except PolificationFailed:
|
| 362 |
+
return expr
|
| 363 |
+
debug('initial gens:', opt.gens)
|
| 364 |
+
ideal, freegens, gens = analyse_gens(opt.gens, hints)
|
| 365 |
+
debug('ideal:', ideal)
|
| 366 |
+
debug('new gens:', gens, " -- len", len(gens))
|
| 367 |
+
debug('free gens:', freegens, " -- len", len(gens))
|
| 368 |
+
# NOTE we force the domain to be ZZ to stop polys from injecting generators
|
| 369 |
+
# (which is usually a sign of a bug in the way we build the ideal)
|
| 370 |
+
if not gens:
|
| 371 |
+
return expr
|
| 372 |
+
G = groebner(ideal, order=order, gens=gens, domain=ZZ)
|
| 373 |
+
debug('groebner basis:', list(G), " -- len", len(G))
|
| 374 |
+
|
| 375 |
+
# If our fraction is a polynomial in the free generators, simplify all
|
| 376 |
+
# coefficients separately:
|
| 377 |
+
|
| 378 |
+
from sympy.simplify.ratsimp import ratsimpmodprime
|
| 379 |
+
|
| 380 |
+
if freegens and pdenom.has_only_gens(*set(gens).intersection(pdenom.gens)):
|
| 381 |
+
num = Poly(num, gens=gens+freegens).eject(*gens)
|
| 382 |
+
res = []
|
| 383 |
+
for monom, coeff in num.terms():
|
| 384 |
+
ourgens = set(parallel_poly_from_expr([coeff, denom])[1].gens)
|
| 385 |
+
# We compute the transitive closure of all generators that can
|
| 386 |
+
# be reached from our generators through relations in the ideal.
|
| 387 |
+
changed = True
|
| 388 |
+
while changed:
|
| 389 |
+
changed = False
|
| 390 |
+
for p in ideal:
|
| 391 |
+
p = Poly(p)
|
| 392 |
+
if not ourgens.issuperset(p.gens) and \
|
| 393 |
+
not p.has_only_gens(*set(p.gens).difference(ourgens)):
|
| 394 |
+
changed = True
|
| 395 |
+
ourgens.update(p.exclude().gens)
|
| 396 |
+
# NOTE preserve order!
|
| 397 |
+
realgens = [x for x in gens if x in ourgens]
|
| 398 |
+
# The generators of the ideal have now been (implicitly) split
|
| 399 |
+
# into two groups: those involving ourgens and those that don't.
|
| 400 |
+
# Since we took the transitive closure above, these two groups
|
| 401 |
+
# live in subgrings generated by a *disjoint* set of variables.
|
| 402 |
+
# Any sensible groebner basis algorithm will preserve this disjoint
|
| 403 |
+
# structure (i.e. the elements of the groebner basis can be split
|
| 404 |
+
# similarly), and and the two subsets of the groebner basis then
|
| 405 |
+
# form groebner bases by themselves. (For the smaller generating
|
| 406 |
+
# sets, of course.)
|
| 407 |
+
ourG = [g.as_expr() for g in G.polys if
|
| 408 |
+
g.has_only_gens(*ourgens.intersection(g.gens))]
|
| 409 |
+
res.append(Mul(*[a**b for a, b in zip(freegens, monom)]) * \
|
| 410 |
+
ratsimpmodprime(coeff/denom, ourG, order=order,
|
| 411 |
+
gens=realgens, quick=quick, domain=ZZ,
|
| 412 |
+
polynomial=polynomial).subs(subs))
|
| 413 |
+
return Add(*res)
|
| 414 |
+
# NOTE The following is simpler and has less assumptions on the
|
| 415 |
+
# groebner basis algorithm. If the above turns out to be broken,
|
| 416 |
+
# use this.
|
| 417 |
+
return Add(*[Mul(*[a**b for a, b in zip(freegens, monom)]) * \
|
| 418 |
+
ratsimpmodprime(coeff/denom, list(G), order=order,
|
| 419 |
+
gens=gens, quick=quick, domain=ZZ)
|
| 420 |
+
for monom, coeff in num.terms()])
|
| 421 |
+
else:
|
| 422 |
+
return ratsimpmodprime(
|
| 423 |
+
expr, list(G), order=order, gens=freegens+gens,
|
| 424 |
+
quick=quick, domain=ZZ, polynomial=polynomial).subs(subs)
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
_trigs = (TrigonometricFunction, HyperbolicFunction)
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
def _trigsimp_inverse(rv):
|
| 431 |
+
|
| 432 |
+
def check_args(x, y):
|
| 433 |
+
try:
|
| 434 |
+
return x.args[0] == y.args[0]
|
| 435 |
+
except IndexError:
|
| 436 |
+
return False
|
| 437 |
+
|
| 438 |
+
def f(rv):
|
| 439 |
+
# for simple functions
|
| 440 |
+
g = getattr(rv, 'inverse', None)
|
| 441 |
+
if (g is not None and isinstance(rv.args[0], g()) and
|
| 442 |
+
isinstance(g()(1), TrigonometricFunction)):
|
| 443 |
+
return rv.args[0].args[0]
|
| 444 |
+
|
| 445 |
+
# for atan2 simplifications, harder because atan2 has 2 args
|
| 446 |
+
if isinstance(rv, atan2):
|
| 447 |
+
y, x = rv.args
|
| 448 |
+
if _coeff_isneg(y):
|
| 449 |
+
return -f(atan2(-y, x))
|
| 450 |
+
elif _coeff_isneg(x):
|
| 451 |
+
return S.Pi - f(atan2(y, -x))
|
| 452 |
+
|
| 453 |
+
if check_args(x, y):
|
| 454 |
+
if isinstance(y, sin) and isinstance(x, cos):
|
| 455 |
+
return x.args[0]
|
| 456 |
+
if isinstance(y, cos) and isinstance(x, sin):
|
| 457 |
+
return S.Pi / 2 - x.args[0]
|
| 458 |
+
|
| 459 |
+
return rv
|
| 460 |
+
|
| 461 |
+
return bottom_up(rv, f)
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
def trigsimp(expr, inverse=False, **opts):
|
| 465 |
+
"""Returns a reduced expression by using known trig identities.
|
| 466 |
+
|
| 467 |
+
Parameters
|
| 468 |
+
==========
|
| 469 |
+
|
| 470 |
+
inverse : bool, optional
|
| 471 |
+
If ``inverse=True``, it will be assumed that a composition of inverse
|
| 472 |
+
functions, such as sin and asin, can be cancelled in any order.
|
| 473 |
+
For example, ``asin(sin(x))`` will yield ``x`` without checking whether
|
| 474 |
+
x belongs to the set where this relation is true. The default is False.
|
| 475 |
+
Default : True
|
| 476 |
+
|
| 477 |
+
method : string, optional
|
| 478 |
+
Specifies the method to use. Valid choices are:
|
| 479 |
+
|
| 480 |
+
- ``'matching'``, default
|
| 481 |
+
- ``'groebner'``
|
| 482 |
+
- ``'combined'``
|
| 483 |
+
- ``'fu'``
|
| 484 |
+
- ``'old'``
|
| 485 |
+
|
| 486 |
+
If ``'matching'``, simplify the expression recursively by targeting
|
| 487 |
+
common patterns. If ``'groebner'``, apply an experimental groebner
|
| 488 |
+
basis algorithm. In this case further options are forwarded to
|
| 489 |
+
``trigsimp_groebner``, please refer to
|
| 490 |
+
its docstring. If ``'combined'``, it first runs the groebner basis
|
| 491 |
+
algorithm with small default parameters, then runs the ``'matching'``
|
| 492 |
+
algorithm. If ``'fu'``, run the collection of trigonometric
|
| 493 |
+
transformations described by Fu, et al. (see the
|
| 494 |
+
:py:func:`~sympy.simplify.fu.fu` docstring). If ``'old'``, the original
|
| 495 |
+
SymPy trig simplification function is run.
|
| 496 |
+
opts :
|
| 497 |
+
Optional keyword arguments passed to the method. See each method's
|
| 498 |
+
function docstring for details.
|
| 499 |
+
|
| 500 |
+
Examples
|
| 501 |
+
========
|
| 502 |
+
|
| 503 |
+
>>> from sympy import trigsimp, sin, cos, log
|
| 504 |
+
>>> from sympy.abc import x
|
| 505 |
+
>>> e = 2*sin(x)**2 + 2*cos(x)**2
|
| 506 |
+
>>> trigsimp(e)
|
| 507 |
+
2
|
| 508 |
+
|
| 509 |
+
Simplification occurs wherever trigonometric functions are located.
|
| 510 |
+
|
| 511 |
+
>>> trigsimp(log(e))
|
| 512 |
+
log(2)
|
| 513 |
+
|
| 514 |
+
Using ``method='groebner'`` (or ``method='combined'``) might lead to
|
| 515 |
+
greater simplification.
|
| 516 |
+
|
| 517 |
+
The old trigsimp routine can be accessed as with method ``method='old'``.
|
| 518 |
+
|
| 519 |
+
>>> from sympy import coth, tanh
|
| 520 |
+
>>> t = 3*tanh(x)**7 - 2/coth(x)**7
|
| 521 |
+
>>> trigsimp(t, method='old') == t
|
| 522 |
+
True
|
| 523 |
+
>>> trigsimp(t)
|
| 524 |
+
tanh(x)**7
|
| 525 |
+
|
| 526 |
+
"""
|
| 527 |
+
from sympy.simplify.fu import fu
|
| 528 |
+
|
| 529 |
+
expr = sympify(expr)
|
| 530 |
+
|
| 531 |
+
_eval_trigsimp = getattr(expr, '_eval_trigsimp', None)
|
| 532 |
+
if _eval_trigsimp is not None:
|
| 533 |
+
return _eval_trigsimp(**opts)
|
| 534 |
+
|
| 535 |
+
old = opts.pop('old', False)
|
| 536 |
+
if not old:
|
| 537 |
+
opts.pop('deep', None)
|
| 538 |
+
opts.pop('recursive', None)
|
| 539 |
+
method = opts.pop('method', 'matching')
|
| 540 |
+
else:
|
| 541 |
+
method = 'old'
|
| 542 |
+
|
| 543 |
+
def groebnersimp(ex, **opts):
|
| 544 |
+
def traverse(e):
|
| 545 |
+
if e.is_Atom:
|
| 546 |
+
return e
|
| 547 |
+
args = [traverse(x) for x in e.args]
|
| 548 |
+
if e.is_Function or e.is_Pow:
|
| 549 |
+
args = [trigsimp_groebner(x, **opts) for x in args]
|
| 550 |
+
return e.func(*args)
|
| 551 |
+
new = traverse(ex)
|
| 552 |
+
if not isinstance(new, Expr):
|
| 553 |
+
return new
|
| 554 |
+
return trigsimp_groebner(new, **opts)
|
| 555 |
+
|
| 556 |
+
trigsimpfunc = {
|
| 557 |
+
'fu': (lambda x: fu(x, **opts)),
|
| 558 |
+
'matching': (lambda x: futrig(x)),
|
| 559 |
+
'groebner': (lambda x: groebnersimp(x, **opts)),
|
| 560 |
+
'combined': (lambda x: futrig(groebnersimp(x,
|
| 561 |
+
polynomial=True, hints=[2, tan]))),
|
| 562 |
+
'old': lambda x: trigsimp_old(x, **opts),
|
| 563 |
+
}[method]
|
| 564 |
+
|
| 565 |
+
expr_simplified = trigsimpfunc(expr)
|
| 566 |
+
if inverse:
|
| 567 |
+
expr_simplified = _trigsimp_inverse(expr_simplified)
|
| 568 |
+
|
| 569 |
+
return expr_simplified
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
def exptrigsimp(expr):
|
| 573 |
+
"""
|
| 574 |
+
Simplifies exponential / trigonometric / hyperbolic functions.
|
| 575 |
+
|
| 576 |
+
Examples
|
| 577 |
+
========
|
| 578 |
+
|
| 579 |
+
>>> from sympy import exptrigsimp, exp, cosh, sinh
|
| 580 |
+
>>> from sympy.abc import z
|
| 581 |
+
|
| 582 |
+
>>> exptrigsimp(exp(z) + exp(-z))
|
| 583 |
+
2*cosh(z)
|
| 584 |
+
>>> exptrigsimp(cosh(z) - sinh(z))
|
| 585 |
+
exp(-z)
|
| 586 |
+
"""
|
| 587 |
+
from sympy.simplify.fu import hyper_as_trig, TR2i
|
| 588 |
+
|
| 589 |
+
def exp_trig(e):
|
| 590 |
+
# select the better of e, and e rewritten in terms of exp or trig
|
| 591 |
+
# functions
|
| 592 |
+
choices = [e]
|
| 593 |
+
if e.has(*_trigs):
|
| 594 |
+
choices.append(e.rewrite(exp))
|
| 595 |
+
choices.append(e.rewrite(cos))
|
| 596 |
+
return min(*choices, key=count_ops)
|
| 597 |
+
newexpr = bottom_up(expr, exp_trig)
|
| 598 |
+
|
| 599 |
+
def f(rv):
|
| 600 |
+
if not rv.is_Mul:
|
| 601 |
+
return rv
|
| 602 |
+
commutative_part, noncommutative_part = rv.args_cnc()
|
| 603 |
+
# Since as_powers_dict loses order information,
|
| 604 |
+
# if there is more than one noncommutative factor,
|
| 605 |
+
# it should only be used to simplify the commutative part.
|
| 606 |
+
if (len(noncommutative_part) > 1):
|
| 607 |
+
return f(Mul(*commutative_part))*Mul(*noncommutative_part)
|
| 608 |
+
rvd = rv.as_powers_dict()
|
| 609 |
+
newd = rvd.copy()
|
| 610 |
+
|
| 611 |
+
def signlog(expr, sign=S.One):
|
| 612 |
+
if expr is S.Exp1:
|
| 613 |
+
return sign, S.One
|
| 614 |
+
elif isinstance(expr, exp) or (expr.is_Pow and expr.base == S.Exp1):
|
| 615 |
+
return sign, expr.exp
|
| 616 |
+
elif sign is S.One:
|
| 617 |
+
return signlog(-expr, sign=-S.One)
|
| 618 |
+
else:
|
| 619 |
+
return None, None
|
| 620 |
+
|
| 621 |
+
ee = rvd[S.Exp1]
|
| 622 |
+
for k in rvd:
|
| 623 |
+
if k.is_Add and len(k.args) == 2:
|
| 624 |
+
# k == c*(1 + sign*E**x)
|
| 625 |
+
c = k.args[0]
|
| 626 |
+
sign, x = signlog(k.args[1]/c)
|
| 627 |
+
if not x:
|
| 628 |
+
continue
|
| 629 |
+
m = rvd[k]
|
| 630 |
+
newd[k] -= m
|
| 631 |
+
if ee == -x*m/2:
|
| 632 |
+
# sinh and cosh
|
| 633 |
+
newd[S.Exp1] -= ee
|
| 634 |
+
ee = 0
|
| 635 |
+
if sign == 1:
|
| 636 |
+
newd[2*c*cosh(x/2)] += m
|
| 637 |
+
else:
|
| 638 |
+
newd[-2*c*sinh(x/2)] += m
|
| 639 |
+
elif newd[1 - sign*S.Exp1**x] == -m:
|
| 640 |
+
# tanh
|
| 641 |
+
del newd[1 - sign*S.Exp1**x]
|
| 642 |
+
if sign == 1:
|
| 643 |
+
newd[-c/tanh(x/2)] += m
|
| 644 |
+
else:
|
| 645 |
+
newd[-c*tanh(x/2)] += m
|
| 646 |
+
else:
|
| 647 |
+
newd[1 + sign*S.Exp1**x] += m
|
| 648 |
+
newd[c] += m
|
| 649 |
+
|
| 650 |
+
return Mul(*[k**newd[k] for k in newd])
|
| 651 |
+
newexpr = bottom_up(newexpr, f)
|
| 652 |
+
|
| 653 |
+
# sin/cos and sinh/cosh ratios to tan and tanh, respectively
|
| 654 |
+
if newexpr.has(HyperbolicFunction):
|
| 655 |
+
e, f = hyper_as_trig(newexpr)
|
| 656 |
+
newexpr = f(TR2i(e))
|
| 657 |
+
if newexpr.has(TrigonometricFunction):
|
| 658 |
+
newexpr = TR2i(newexpr)
|
| 659 |
+
|
| 660 |
+
# can we ever generate an I where there was none previously?
|
| 661 |
+
if not (newexpr.has(I) and not expr.has(I)):
|
| 662 |
+
expr = newexpr
|
| 663 |
+
return expr
|
| 664 |
+
|
| 665 |
+
#-------------------- the old trigsimp routines ---------------------
|
| 666 |
+
|
| 667 |
+
def trigsimp_old(expr, *, first=True, **opts):
|
| 668 |
+
"""
|
| 669 |
+
Reduces expression by using known trig identities.
|
| 670 |
+
|
| 671 |
+
Notes
|
| 672 |
+
=====
|
| 673 |
+
|
| 674 |
+
deep:
|
| 675 |
+
- Apply trigsimp inside all objects with arguments
|
| 676 |
+
|
| 677 |
+
recursive:
|
| 678 |
+
- Use common subexpression elimination (cse()) and apply
|
| 679 |
+
trigsimp recursively (this is quite expensive if the
|
| 680 |
+
expression is large)
|
| 681 |
+
|
| 682 |
+
method:
|
| 683 |
+
- Determine the method to use. Valid choices are 'matching' (default),
|
| 684 |
+
'groebner', 'combined', 'fu' and 'futrig'. If 'matching', simplify the
|
| 685 |
+
expression recursively by pattern matching. If 'groebner', apply an
|
| 686 |
+
experimental groebner basis algorithm. In this case further options
|
| 687 |
+
are forwarded to ``trigsimp_groebner``, please refer to its docstring.
|
| 688 |
+
If 'combined', first run the groebner basis algorithm with small
|
| 689 |
+
default parameters, then run the 'matching' algorithm. 'fu' runs the
|
| 690 |
+
collection of trigonometric transformations described by Fu, et al.
|
| 691 |
+
(see the `fu` docstring) while `futrig` runs a subset of Fu-transforms
|
| 692 |
+
that mimic the behavior of `trigsimp`.
|
| 693 |
+
|
| 694 |
+
compare:
|
| 695 |
+
- show input and output from `trigsimp` and `futrig` when different,
|
| 696 |
+
but returns the `trigsimp` value.
|
| 697 |
+
|
| 698 |
+
Examples
|
| 699 |
+
========
|
| 700 |
+
|
| 701 |
+
>>> from sympy import trigsimp, sin, cos, log, cot
|
| 702 |
+
>>> from sympy.abc import x
|
| 703 |
+
>>> e = 2*sin(x)**2 + 2*cos(x)**2
|
| 704 |
+
>>> trigsimp(e, old=True)
|
| 705 |
+
2
|
| 706 |
+
>>> trigsimp(log(e), old=True)
|
| 707 |
+
log(2*sin(x)**2 + 2*cos(x)**2)
|
| 708 |
+
>>> trigsimp(log(e), deep=True, old=True)
|
| 709 |
+
log(2)
|
| 710 |
+
|
| 711 |
+
Using `method="groebner"` (or `"combined"`) can sometimes lead to a lot
|
| 712 |
+
more simplification:
|
| 713 |
+
|
| 714 |
+
>>> e = (-sin(x) + 1)/cos(x) + cos(x)/(-sin(x) + 1)
|
| 715 |
+
>>> trigsimp(e, old=True)
|
| 716 |
+
(1 - sin(x))/cos(x) + cos(x)/(1 - sin(x))
|
| 717 |
+
>>> trigsimp(e, method="groebner", old=True)
|
| 718 |
+
2/cos(x)
|
| 719 |
+
|
| 720 |
+
>>> trigsimp(1/cot(x)**2, compare=True, old=True)
|
| 721 |
+
futrig: tan(x)**2
|
| 722 |
+
cot(x)**(-2)
|
| 723 |
+
|
| 724 |
+
"""
|
| 725 |
+
old = expr
|
| 726 |
+
if first:
|
| 727 |
+
if not expr.has(*_trigs):
|
| 728 |
+
return expr
|
| 729 |
+
|
| 730 |
+
trigsyms = set().union(*[t.free_symbols for t in expr.atoms(*_trigs)])
|
| 731 |
+
if len(trigsyms) > 1:
|
| 732 |
+
from sympy.simplify.simplify import separatevars
|
| 733 |
+
|
| 734 |
+
d = separatevars(expr)
|
| 735 |
+
if d.is_Mul:
|
| 736 |
+
d = separatevars(d, dict=True) or d
|
| 737 |
+
if isinstance(d, dict):
|
| 738 |
+
expr = 1
|
| 739 |
+
for v in d.values():
|
| 740 |
+
# remove hollow factoring
|
| 741 |
+
was = v
|
| 742 |
+
v = expand_mul(v)
|
| 743 |
+
opts['first'] = False
|
| 744 |
+
vnew = trigsimp(v, **opts)
|
| 745 |
+
if vnew == v:
|
| 746 |
+
vnew = was
|
| 747 |
+
expr *= vnew
|
| 748 |
+
old = expr
|
| 749 |
+
else:
|
| 750 |
+
if d.is_Add:
|
| 751 |
+
for s in trigsyms:
|
| 752 |
+
r, e = expr.as_independent(s)
|
| 753 |
+
if r:
|
| 754 |
+
opts['first'] = False
|
| 755 |
+
expr = r + trigsimp(e, **opts)
|
| 756 |
+
if not expr.is_Add:
|
| 757 |
+
break
|
| 758 |
+
old = expr
|
| 759 |
+
|
| 760 |
+
recursive = opts.pop('recursive', False)
|
| 761 |
+
deep = opts.pop('deep', False)
|
| 762 |
+
method = opts.pop('method', 'matching')
|
| 763 |
+
|
| 764 |
+
def groebnersimp(ex, deep, **opts):
|
| 765 |
+
def traverse(e):
|
| 766 |
+
if e.is_Atom:
|
| 767 |
+
return e
|
| 768 |
+
args = [traverse(x) for x in e.args]
|
| 769 |
+
if e.is_Function or e.is_Pow:
|
| 770 |
+
args = [trigsimp_groebner(x, **opts) for x in args]
|
| 771 |
+
return e.func(*args)
|
| 772 |
+
if deep:
|
| 773 |
+
ex = traverse(ex)
|
| 774 |
+
return trigsimp_groebner(ex, **opts)
|
| 775 |
+
|
| 776 |
+
trigsimpfunc = {
|
| 777 |
+
'matching': (lambda x, d: _trigsimp(x, d)),
|
| 778 |
+
'groebner': (lambda x, d: groebnersimp(x, d, **opts)),
|
| 779 |
+
'combined': (lambda x, d: _trigsimp(groebnersimp(x,
|
| 780 |
+
d, polynomial=True, hints=[2, tan]),
|
| 781 |
+
d))
|
| 782 |
+
}[method]
|
| 783 |
+
|
| 784 |
+
if recursive:
|
| 785 |
+
w, g = cse(expr)
|
| 786 |
+
g = trigsimpfunc(g[0], deep)
|
| 787 |
+
|
| 788 |
+
for sub in reversed(w):
|
| 789 |
+
g = g.subs(sub[0], sub[1])
|
| 790 |
+
g = trigsimpfunc(g, deep)
|
| 791 |
+
result = g
|
| 792 |
+
else:
|
| 793 |
+
result = trigsimpfunc(expr, deep)
|
| 794 |
+
|
| 795 |
+
if opts.get('compare', False):
|
| 796 |
+
f = futrig(old)
|
| 797 |
+
if f != result:
|
| 798 |
+
print('\tfutrig:', f)
|
| 799 |
+
|
| 800 |
+
return result
|
| 801 |
+
|
| 802 |
+
|
| 803 |
+
def _dotrig(a, b):
|
| 804 |
+
"""Helper to tell whether ``a`` and ``b`` have the same sorts
|
| 805 |
+
of symbols in them -- no need to test hyperbolic patterns against
|
| 806 |
+
expressions that have no hyperbolics in them."""
|
| 807 |
+
return a.func == b.func and (
|
| 808 |
+
a.has(TrigonometricFunction) and b.has(TrigonometricFunction) or
|
| 809 |
+
a.has(HyperbolicFunction) and b.has(HyperbolicFunction))
|
| 810 |
+
|
| 811 |
+
|
| 812 |
+
_trigpat = None
|
| 813 |
+
def _trigpats():
|
| 814 |
+
global _trigpat
|
| 815 |
+
a, b, c = symbols('a b c', cls=Wild)
|
| 816 |
+
d = Wild('d', commutative=False)
|
| 817 |
+
|
| 818 |
+
# for the simplifications like sinh/cosh -> tanh:
|
| 819 |
+
# DO NOT REORDER THE FIRST 14 since these are assumed to be in this
|
| 820 |
+
# order in _match_div_rewrite.
|
| 821 |
+
matchers_division = (
|
| 822 |
+
(a*sin(b)**c/cos(b)**c, a*tan(b)**c, sin(b), cos(b)),
|
| 823 |
+
(a*tan(b)**c*cos(b)**c, a*sin(b)**c, sin(b), cos(b)),
|
| 824 |
+
(a*cot(b)**c*sin(b)**c, a*cos(b)**c, sin(b), cos(b)),
|
| 825 |
+
(a*tan(b)**c/sin(b)**c, a/cos(b)**c, sin(b), cos(b)),
|
| 826 |
+
(a*cot(b)**c/cos(b)**c, a/sin(b)**c, sin(b), cos(b)),
|
| 827 |
+
(a*cot(b)**c*tan(b)**c, a, sin(b), cos(b)),
|
| 828 |
+
(a*(cos(b) + 1)**c*(cos(b) - 1)**c,
|
| 829 |
+
a*(-sin(b)**2)**c, cos(b) + 1, cos(b) - 1),
|
| 830 |
+
(a*(sin(b) + 1)**c*(sin(b) - 1)**c,
|
| 831 |
+
a*(-cos(b)**2)**c, sin(b) + 1, sin(b) - 1),
|
| 832 |
+
|
| 833 |
+
(a*sinh(b)**c/cosh(b)**c, a*tanh(b)**c, S.One, S.One),
|
| 834 |
+
(a*tanh(b)**c*cosh(b)**c, a*sinh(b)**c, S.One, S.One),
|
| 835 |
+
(a*coth(b)**c*sinh(b)**c, a*cosh(b)**c, S.One, S.One),
|
| 836 |
+
(a*tanh(b)**c/sinh(b)**c, a/cosh(b)**c, S.One, S.One),
|
| 837 |
+
(a*coth(b)**c/cosh(b)**c, a/sinh(b)**c, S.One, S.One),
|
| 838 |
+
(a*coth(b)**c*tanh(b)**c, a, S.One, S.One),
|
| 839 |
+
|
| 840 |
+
(c*(tanh(a) + tanh(b))/(1 + tanh(a)*tanh(b)),
|
| 841 |
+
tanh(a + b)*c, S.One, S.One),
|
| 842 |
+
)
|
| 843 |
+
|
| 844 |
+
matchers_add = (
|
| 845 |
+
(c*sin(a)*cos(b) + c*cos(a)*sin(b) + d, sin(a + b)*c + d),
|
| 846 |
+
(c*cos(a)*cos(b) - c*sin(a)*sin(b) + d, cos(a + b)*c + d),
|
| 847 |
+
(c*sin(a)*cos(b) - c*cos(a)*sin(b) + d, sin(a - b)*c + d),
|
| 848 |
+
(c*cos(a)*cos(b) + c*sin(a)*sin(b) + d, cos(a - b)*c + d),
|
| 849 |
+
(c*sinh(a)*cosh(b) + c*sinh(b)*cosh(a) + d, sinh(a + b)*c + d),
|
| 850 |
+
(c*cosh(a)*cosh(b) + c*sinh(a)*sinh(b) + d, cosh(a + b)*c + d),
|
| 851 |
+
)
|
| 852 |
+
|
| 853 |
+
# for cos(x)**2 + sin(x)**2 -> 1
|
| 854 |
+
matchers_identity = (
|
| 855 |
+
(a*sin(b)**2, a - a*cos(b)**2),
|
| 856 |
+
(a*tan(b)**2, a*(1/cos(b))**2 - a),
|
| 857 |
+
(a*cot(b)**2, a*(1/sin(b))**2 - a),
|
| 858 |
+
(a*sin(b + c), a*(sin(b)*cos(c) + sin(c)*cos(b))),
|
| 859 |
+
(a*cos(b + c), a*(cos(b)*cos(c) - sin(b)*sin(c))),
|
| 860 |
+
(a*tan(b + c), a*((tan(b) + tan(c))/(1 - tan(b)*tan(c)))),
|
| 861 |
+
|
| 862 |
+
(a*sinh(b)**2, a*cosh(b)**2 - a),
|
| 863 |
+
(a*tanh(b)**2, a - a*(1/cosh(b))**2),
|
| 864 |
+
(a*coth(b)**2, a + a*(1/sinh(b))**2),
|
| 865 |
+
(a*sinh(b + c), a*(sinh(b)*cosh(c) + sinh(c)*cosh(b))),
|
| 866 |
+
(a*cosh(b + c), a*(cosh(b)*cosh(c) + sinh(b)*sinh(c))),
|
| 867 |
+
(a*tanh(b + c), a*((tanh(b) + tanh(c))/(1 + tanh(b)*tanh(c)))),
|
| 868 |
+
|
| 869 |
+
)
|
| 870 |
+
|
| 871 |
+
# Reduce any lingering artifacts, such as sin(x)**2 changing
|
| 872 |
+
# to 1-cos(x)**2 when sin(x)**2 was "simpler"
|
| 873 |
+
artifacts = (
|
| 874 |
+
(a - a*cos(b)**2 + c, a*sin(b)**2 + c, cos),
|
| 875 |
+
(a - a*(1/cos(b))**2 + c, -a*tan(b)**2 + c, cos),
|
| 876 |
+
(a - a*(1/sin(b))**2 + c, -a*cot(b)**2 + c, sin),
|
| 877 |
+
|
| 878 |
+
(a - a*cosh(b)**2 + c, -a*sinh(b)**2 + c, cosh),
|
| 879 |
+
(a - a*(1/cosh(b))**2 + c, a*tanh(b)**2 + c, cosh),
|
| 880 |
+
(a + a*(1/sinh(b))**2 + c, a*coth(b)**2 + c, sinh),
|
| 881 |
+
|
| 882 |
+
# same as above but with noncommutative prefactor
|
| 883 |
+
(a*d - a*d*cos(b)**2 + c, a*d*sin(b)**2 + c, cos),
|
| 884 |
+
(a*d - a*d*(1/cos(b))**2 + c, -a*d*tan(b)**2 + c, cos),
|
| 885 |
+
(a*d - a*d*(1/sin(b))**2 + c, -a*d*cot(b)**2 + c, sin),
|
| 886 |
+
|
| 887 |
+
(a*d - a*d*cosh(b)**2 + c, -a*d*sinh(b)**2 + c, cosh),
|
| 888 |
+
(a*d - a*d*(1/cosh(b))**2 + c, a*d*tanh(b)**2 + c, cosh),
|
| 889 |
+
(a*d + a*d*(1/sinh(b))**2 + c, a*d*coth(b)**2 + c, sinh),
|
| 890 |
+
)
|
| 891 |
+
|
| 892 |
+
_trigpat = (a, b, c, d, matchers_division, matchers_add,
|
| 893 |
+
matchers_identity, artifacts)
|
| 894 |
+
return _trigpat
|
| 895 |
+
|
| 896 |
+
|
| 897 |
+
def _replace_mul_fpowxgpow(expr, f, g, rexp, h, rexph):
|
| 898 |
+
"""Helper for _match_div_rewrite.
|
| 899 |
+
|
| 900 |
+
Replace f(b_)**c_*g(b_)**(rexp(c_)) with h(b)**rexph(c) if f(b_)
|
| 901 |
+
and g(b_) are both positive or if c_ is an integer.
|
| 902 |
+
"""
|
| 903 |
+
# assert expr.is_Mul and expr.is_commutative and f != g
|
| 904 |
+
fargs = defaultdict(int)
|
| 905 |
+
gargs = defaultdict(int)
|
| 906 |
+
args = []
|
| 907 |
+
for x in expr.args:
|
| 908 |
+
if x.is_Pow or x.func in (f, g):
|
| 909 |
+
b, e = x.as_base_exp()
|
| 910 |
+
if b.is_positive or e.is_integer:
|
| 911 |
+
if b.func == f:
|
| 912 |
+
fargs[b.args[0]] += e
|
| 913 |
+
continue
|
| 914 |
+
elif b.func == g:
|
| 915 |
+
gargs[b.args[0]] += e
|
| 916 |
+
continue
|
| 917 |
+
args.append(x)
|
| 918 |
+
common = set(fargs) & set(gargs)
|
| 919 |
+
hit = False
|
| 920 |
+
while common:
|
| 921 |
+
key = common.pop()
|
| 922 |
+
fe = fargs.pop(key)
|
| 923 |
+
ge = gargs.pop(key)
|
| 924 |
+
if fe == rexp(ge):
|
| 925 |
+
args.append(h(key)**rexph(fe))
|
| 926 |
+
hit = True
|
| 927 |
+
else:
|
| 928 |
+
fargs[key] = fe
|
| 929 |
+
gargs[key] = ge
|
| 930 |
+
if not hit:
|
| 931 |
+
return expr
|
| 932 |
+
while fargs:
|
| 933 |
+
key, e = fargs.popitem()
|
| 934 |
+
args.append(f(key)**e)
|
| 935 |
+
while gargs:
|
| 936 |
+
key, e = gargs.popitem()
|
| 937 |
+
args.append(g(key)**e)
|
| 938 |
+
return Mul(*args)
|
| 939 |
+
|
| 940 |
+
|
| 941 |
+
_idn = lambda x: x
|
| 942 |
+
_midn = lambda x: -x
|
| 943 |
+
_one = lambda x: S.One
|
| 944 |
+
|
| 945 |
+
def _match_div_rewrite(expr, i):
|
| 946 |
+
"""helper for __trigsimp"""
|
| 947 |
+
if i == 0:
|
| 948 |
+
expr = _replace_mul_fpowxgpow(expr, sin, cos,
|
| 949 |
+
_midn, tan, _idn)
|
| 950 |
+
elif i == 1:
|
| 951 |
+
expr = _replace_mul_fpowxgpow(expr, tan, cos,
|
| 952 |
+
_idn, sin, _idn)
|
| 953 |
+
elif i == 2:
|
| 954 |
+
expr = _replace_mul_fpowxgpow(expr, cot, sin,
|
| 955 |
+
_idn, cos, _idn)
|
| 956 |
+
elif i == 3:
|
| 957 |
+
expr = _replace_mul_fpowxgpow(expr, tan, sin,
|
| 958 |
+
_midn, cos, _midn)
|
| 959 |
+
elif i == 4:
|
| 960 |
+
expr = _replace_mul_fpowxgpow(expr, cot, cos,
|
| 961 |
+
_midn, sin, _midn)
|
| 962 |
+
elif i == 5:
|
| 963 |
+
expr = _replace_mul_fpowxgpow(expr, cot, tan,
|
| 964 |
+
_idn, _one, _idn)
|
| 965 |
+
# i in (6, 7) is skipped
|
| 966 |
+
elif i == 8:
|
| 967 |
+
expr = _replace_mul_fpowxgpow(expr, sinh, cosh,
|
| 968 |
+
_midn, tanh, _idn)
|
| 969 |
+
elif i == 9:
|
| 970 |
+
expr = _replace_mul_fpowxgpow(expr, tanh, cosh,
|
| 971 |
+
_idn, sinh, _idn)
|
| 972 |
+
elif i == 10:
|
| 973 |
+
expr = _replace_mul_fpowxgpow(expr, coth, sinh,
|
| 974 |
+
_idn, cosh, _idn)
|
| 975 |
+
elif i == 11:
|
| 976 |
+
expr = _replace_mul_fpowxgpow(expr, tanh, sinh,
|
| 977 |
+
_midn, cosh, _midn)
|
| 978 |
+
elif i == 12:
|
| 979 |
+
expr = _replace_mul_fpowxgpow(expr, coth, cosh,
|
| 980 |
+
_midn, sinh, _midn)
|
| 981 |
+
elif i == 13:
|
| 982 |
+
expr = _replace_mul_fpowxgpow(expr, coth, tanh,
|
| 983 |
+
_idn, _one, _idn)
|
| 984 |
+
else:
|
| 985 |
+
return None
|
| 986 |
+
return expr
|
| 987 |
+
|
| 988 |
+
|
| 989 |
+
def _trigsimp(expr, deep=False):
|
| 990 |
+
# protect the cache from non-trig patterns; we only allow
|
| 991 |
+
# trig patterns to enter the cache
|
| 992 |
+
if expr.has(*_trigs):
|
| 993 |
+
return __trigsimp(expr, deep)
|
| 994 |
+
return expr
|
| 995 |
+
|
| 996 |
+
|
| 997 |
+
@cacheit
|
| 998 |
+
def __trigsimp(expr, deep=False):
|
| 999 |
+
"""recursive helper for trigsimp"""
|
| 1000 |
+
from sympy.simplify.fu import TR10i
|
| 1001 |
+
|
| 1002 |
+
if _trigpat is None:
|
| 1003 |
+
_trigpats()
|
| 1004 |
+
a, b, c, d, matchers_division, matchers_add, \
|
| 1005 |
+
matchers_identity, artifacts = _trigpat
|
| 1006 |
+
|
| 1007 |
+
if expr.is_Mul:
|
| 1008 |
+
# do some simplifications like sin/cos -> tan:
|
| 1009 |
+
if not expr.is_commutative:
|
| 1010 |
+
com, nc = expr.args_cnc()
|
| 1011 |
+
expr = _trigsimp(Mul._from_args(com), deep)*Mul._from_args(nc)
|
| 1012 |
+
else:
|
| 1013 |
+
for i, (pattern, simp, ok1, ok2) in enumerate(matchers_division):
|
| 1014 |
+
if not _dotrig(expr, pattern):
|
| 1015 |
+
continue
|
| 1016 |
+
|
| 1017 |
+
newexpr = _match_div_rewrite(expr, i)
|
| 1018 |
+
if newexpr is not None:
|
| 1019 |
+
if newexpr != expr:
|
| 1020 |
+
expr = newexpr
|
| 1021 |
+
break
|
| 1022 |
+
else:
|
| 1023 |
+
continue
|
| 1024 |
+
|
| 1025 |
+
# use SymPy matching instead
|
| 1026 |
+
res = expr.match(pattern)
|
| 1027 |
+
if res and res.get(c, 0):
|
| 1028 |
+
if not res[c].is_integer:
|
| 1029 |
+
ok = ok1.subs(res)
|
| 1030 |
+
if not ok.is_positive:
|
| 1031 |
+
continue
|
| 1032 |
+
ok = ok2.subs(res)
|
| 1033 |
+
if not ok.is_positive:
|
| 1034 |
+
continue
|
| 1035 |
+
# if "a" contains any of trig or hyperbolic funcs with
|
| 1036 |
+
# argument "b" then skip the simplification
|
| 1037 |
+
if any(w.args[0] == res[b] for w in res[a].atoms(
|
| 1038 |
+
TrigonometricFunction, HyperbolicFunction)):
|
| 1039 |
+
continue
|
| 1040 |
+
# simplify and finish:
|
| 1041 |
+
expr = simp.subs(res)
|
| 1042 |
+
break # process below
|
| 1043 |
+
|
| 1044 |
+
if expr.is_Add:
|
| 1045 |
+
args = []
|
| 1046 |
+
for term in expr.args:
|
| 1047 |
+
if not term.is_commutative:
|
| 1048 |
+
com, nc = term.args_cnc()
|
| 1049 |
+
nc = Mul._from_args(nc)
|
| 1050 |
+
term = Mul._from_args(com)
|
| 1051 |
+
else:
|
| 1052 |
+
nc = S.One
|
| 1053 |
+
term = _trigsimp(term, deep)
|
| 1054 |
+
for pattern, result in matchers_identity:
|
| 1055 |
+
res = term.match(pattern)
|
| 1056 |
+
if res is not None:
|
| 1057 |
+
term = result.subs(res)
|
| 1058 |
+
break
|
| 1059 |
+
args.append(term*nc)
|
| 1060 |
+
if args != expr.args:
|
| 1061 |
+
expr = Add(*args)
|
| 1062 |
+
expr = min(expr, expand(expr), key=count_ops)
|
| 1063 |
+
if expr.is_Add:
|
| 1064 |
+
for pattern, result in matchers_add:
|
| 1065 |
+
if not _dotrig(expr, pattern):
|
| 1066 |
+
continue
|
| 1067 |
+
expr = TR10i(expr)
|
| 1068 |
+
if expr.has(HyperbolicFunction):
|
| 1069 |
+
res = expr.match(pattern)
|
| 1070 |
+
# if "d" contains any trig or hyperbolic funcs with
|
| 1071 |
+
# argument "a" or "b" then skip the simplification;
|
| 1072 |
+
# this isn't perfect -- see tests
|
| 1073 |
+
if res is None or not (a in res and b in res) or any(
|
| 1074 |
+
w.args[0] in (res[a], res[b]) for w in res[d].atoms(
|
| 1075 |
+
TrigonometricFunction, HyperbolicFunction)):
|
| 1076 |
+
continue
|
| 1077 |
+
expr = result.subs(res)
|
| 1078 |
+
break
|
| 1079 |
+
|
| 1080 |
+
# Reduce any lingering artifacts, such as sin(x)**2 changing
|
| 1081 |
+
# to 1 - cos(x)**2 when sin(x)**2 was "simpler"
|
| 1082 |
+
for pattern, result, ex in artifacts:
|
| 1083 |
+
if not _dotrig(expr, pattern):
|
| 1084 |
+
continue
|
| 1085 |
+
# Substitute a new wild that excludes some function(s)
|
| 1086 |
+
# to help influence a better match. This is because
|
| 1087 |
+
# sometimes, for example, 'a' would match sec(x)**2
|
| 1088 |
+
a_t = Wild('a', exclude=[ex])
|
| 1089 |
+
pattern = pattern.subs(a, a_t)
|
| 1090 |
+
result = result.subs(a, a_t)
|
| 1091 |
+
|
| 1092 |
+
m = expr.match(pattern)
|
| 1093 |
+
was = None
|
| 1094 |
+
while m and was != expr:
|
| 1095 |
+
was = expr
|
| 1096 |
+
if m[a_t] == 0 or \
|
| 1097 |
+
-m[a_t] in m[c].args or m[a_t] + m[c] == 0:
|
| 1098 |
+
break
|
| 1099 |
+
if d in m and m[a_t]*m[d] + m[c] == 0:
|
| 1100 |
+
break
|
| 1101 |
+
expr = result.subs(m)
|
| 1102 |
+
m = expr.match(pattern)
|
| 1103 |
+
m.setdefault(c, S.Zero)
|
| 1104 |
+
|
| 1105 |
+
elif expr.is_Mul or expr.is_Pow or deep and expr.args:
|
| 1106 |
+
expr = expr.func(*[_trigsimp(a, deep) for a in expr.args])
|
| 1107 |
+
|
| 1108 |
+
try:
|
| 1109 |
+
if not expr.has(*_trigs):
|
| 1110 |
+
raise TypeError
|
| 1111 |
+
e = expr.atoms(exp)
|
| 1112 |
+
new = expr.rewrite(exp, deep=deep)
|
| 1113 |
+
if new == e:
|
| 1114 |
+
raise TypeError
|
| 1115 |
+
fnew = factor(new)
|
| 1116 |
+
if fnew != new:
|
| 1117 |
+
new = min([new, factor(new)], key=count_ops)
|
| 1118 |
+
# if all exp that were introduced disappeared then accept it
|
| 1119 |
+
if not (new.atoms(exp) - e):
|
| 1120 |
+
expr = new
|
| 1121 |
+
except TypeError:
|
| 1122 |
+
pass
|
| 1123 |
+
|
| 1124 |
+
return expr
|
| 1125 |
+
#------------------- end of old trigsimp routines --------------------
|
| 1126 |
+
|
| 1127 |
+
|
| 1128 |
+
def futrig(e, *, hyper=True, **kwargs):
|
| 1129 |
+
"""Return simplified ``e`` using Fu-like transformations.
|
| 1130 |
+
This is not the "Fu" algorithm. This is called by default
|
| 1131 |
+
from ``trigsimp``. By default, hyperbolics subexpressions
|
| 1132 |
+
will be simplified, but this can be disabled by setting
|
| 1133 |
+
``hyper=False``.
|
| 1134 |
+
|
| 1135 |
+
Examples
|
| 1136 |
+
========
|
| 1137 |
+
|
| 1138 |
+
>>> from sympy import trigsimp, tan, sinh, tanh
|
| 1139 |
+
>>> from sympy.simplify.trigsimp import futrig
|
| 1140 |
+
>>> from sympy.abc import x
|
| 1141 |
+
>>> trigsimp(1/tan(x)**2)
|
| 1142 |
+
tan(x)**(-2)
|
| 1143 |
+
|
| 1144 |
+
>>> futrig(sinh(x)/tanh(x))
|
| 1145 |
+
cosh(x)
|
| 1146 |
+
|
| 1147 |
+
"""
|
| 1148 |
+
from sympy.simplify.fu import hyper_as_trig
|
| 1149 |
+
|
| 1150 |
+
e = sympify(e)
|
| 1151 |
+
|
| 1152 |
+
if not isinstance(e, Basic):
|
| 1153 |
+
return e
|
| 1154 |
+
|
| 1155 |
+
if not e.args:
|
| 1156 |
+
return e
|
| 1157 |
+
|
| 1158 |
+
old = e
|
| 1159 |
+
e = bottom_up(e, _futrig)
|
| 1160 |
+
|
| 1161 |
+
if hyper and e.has(HyperbolicFunction):
|
| 1162 |
+
e, f = hyper_as_trig(e)
|
| 1163 |
+
e = f(bottom_up(e, _futrig))
|
| 1164 |
+
|
| 1165 |
+
if e != old and e.is_Mul and e.args[0].is_Rational:
|
| 1166 |
+
# redistribute leading coeff on 2-arg Add
|
| 1167 |
+
e = Mul(*e.as_coeff_Mul())
|
| 1168 |
+
return e
|
| 1169 |
+
|
| 1170 |
+
|
| 1171 |
+
def _futrig(e):
|
| 1172 |
+
"""Helper for futrig."""
|
| 1173 |
+
from sympy.simplify.fu import (
|
| 1174 |
+
TR1, TR2, TR3, TR2i, TR10, L, TR10i,
|
| 1175 |
+
TR8, TR6, TR15, TR16, TR111, TR5, TRmorrie, TR11, _TR11, TR14, TR22,
|
| 1176 |
+
TR12)
|
| 1177 |
+
|
| 1178 |
+
if not e.has(TrigonometricFunction):
|
| 1179 |
+
return e
|
| 1180 |
+
|
| 1181 |
+
if e.is_Mul:
|
| 1182 |
+
coeff, e = e.as_independent(TrigonometricFunction)
|
| 1183 |
+
else:
|
| 1184 |
+
coeff = None
|
| 1185 |
+
|
| 1186 |
+
Lops = lambda x: (L(x), x.count_ops(), _nodes(x), len(x.args), x.is_Add)
|
| 1187 |
+
trigs = lambda x: x.has(TrigonometricFunction)
|
| 1188 |
+
|
| 1189 |
+
tree = [identity,
|
| 1190 |
+
(
|
| 1191 |
+
TR3, # canonical angles
|
| 1192 |
+
TR1, # sec-csc -> cos-sin
|
| 1193 |
+
TR12, # expand tan of sum
|
| 1194 |
+
lambda x: _eapply(factor, x, trigs),
|
| 1195 |
+
TR2, # tan-cot -> sin-cos
|
| 1196 |
+
[identity, lambda x: _eapply(_mexpand, x, trigs)],
|
| 1197 |
+
TR2i, # sin-cos ratio -> tan
|
| 1198 |
+
lambda x: _eapply(lambda i: factor(i.normal()), x, trigs),
|
| 1199 |
+
TR14, # factored identities
|
| 1200 |
+
TR5, # sin-pow -> cos_pow
|
| 1201 |
+
TR10, # sin-cos of sums -> sin-cos prod
|
| 1202 |
+
TR11, _TR11, TR6, # reduce double angles and rewrite cos pows
|
| 1203 |
+
lambda x: _eapply(factor, x, trigs),
|
| 1204 |
+
TR14, # factored powers of identities
|
| 1205 |
+
[identity, lambda x: _eapply(_mexpand, x, trigs)],
|
| 1206 |
+
TR10i, # sin-cos products > sin-cos of sums
|
| 1207 |
+
TRmorrie,
|
| 1208 |
+
[identity, TR8], # sin-cos products -> sin-cos of sums
|
| 1209 |
+
[identity, lambda x: TR2i(TR2(x))], # tan -> sin-cos -> tan
|
| 1210 |
+
[
|
| 1211 |
+
lambda x: _eapply(expand_mul, TR5(x), trigs),
|
| 1212 |
+
lambda x: _eapply(
|
| 1213 |
+
expand_mul, TR15(x), trigs)], # pos/neg powers of sin
|
| 1214 |
+
[
|
| 1215 |
+
lambda x: _eapply(expand_mul, TR6(x), trigs),
|
| 1216 |
+
lambda x: _eapply(
|
| 1217 |
+
expand_mul, TR16(x), trigs)], # pos/neg powers of cos
|
| 1218 |
+
TR111, # tan, sin, cos to neg power -> cot, csc, sec
|
| 1219 |
+
[identity, TR2i], # sin-cos ratio to tan
|
| 1220 |
+
[identity, lambda x: _eapply(
|
| 1221 |
+
expand_mul, TR22(x), trigs)], # tan-cot to sec-csc
|
| 1222 |
+
TR1, TR2, TR2i,
|
| 1223 |
+
[identity, lambda x: _eapply(
|
| 1224 |
+
factor_terms, TR12(x), trigs)], # expand tan of sum
|
| 1225 |
+
)]
|
| 1226 |
+
e = greedy(tree, objective=Lops)(e)
|
| 1227 |
+
|
| 1228 |
+
if coeff is not None:
|
| 1229 |
+
e = coeff * e
|
| 1230 |
+
|
| 1231 |
+
return e
|
| 1232 |
+
|
| 1233 |
+
|
| 1234 |
+
def _is_Expr(e):
|
| 1235 |
+
"""_eapply helper to tell whether ``e`` and all its args
|
| 1236 |
+
are Exprs."""
|
| 1237 |
+
if isinstance(e, Derivative):
|
| 1238 |
+
return _is_Expr(e.expr)
|
| 1239 |
+
if not isinstance(e, Expr):
|
| 1240 |
+
return False
|
| 1241 |
+
return all(_is_Expr(i) for i in e.args)
|
| 1242 |
+
|
| 1243 |
+
|
| 1244 |
+
def _eapply(func, e, cond=None):
|
| 1245 |
+
"""Apply ``func`` to ``e`` if all args are Exprs else only
|
| 1246 |
+
apply it to those args that *are* Exprs."""
|
| 1247 |
+
if not isinstance(e, Expr):
|
| 1248 |
+
return e
|
| 1249 |
+
if _is_Expr(e) or not e.args:
|
| 1250 |
+
return func(e)
|
| 1251 |
+
return e.func(*[
|
| 1252 |
+
_eapply(func, ei) if (cond is None or cond(ei)) else ei
|
| 1253 |
+
for ei in e.args])
|